Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/policies/deposits/ConvertibleDepositAuctioneer.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
/// forge-lint: disable-start(mixed-case-function, screaming-snake-case-const)
pragma solidity >=0.8.20;
// Libraries
import {ReentrancyGuard} from "@solmate-6.2.0/utils/ReentrancyGuard.sol";
import {FixedPointMathLib} from "@solmate-6.2.0/utils/FixedPointMathLib.sol";
import {FullMath} from "src/libraries/FullMath.sol";
import {EnumerableSet} from "@openzeppelin-5.3.0/utils/structs/EnumerableSet.sol";
// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IConvertibleDepositAuctioneer} from "src/policies/interfaces/deposits/IConvertibleDepositAuctioneer.sol";
import {IConvertibleDepositFacility} from "src/policies/interfaces/deposits/IConvertibleDepositFacility.sol";
// Bophades dependencies
import {Kernel, Keycode, Permissions, Policy, toKeycode} from "src/Kernel.sol";
import {ROLESv1} from "src/modules/ROLES/ROLES.v1.sol";
import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol";
import {ConvertibleDepositFacility} from "src/policies/deposits/ConvertibleDepositFacility.sol";
/// @title Convertible Deposit Auctioneer
/// @notice Implementation of the {IConvertibleDepositAuctioneer} interface for a specific deposit token and 1 or more deposit periods
/// @dev This contract implements an auction for convertible deposit tokens. It runs these auctions according to the following principles:
/// - Auctions are of infinite duration
/// - Auctions are of infinite capacity
/// - Users place bids by supplying an amount of the configured bid token
/// - The payout token is a receipt token (managed by {DepositManager}), which can be converted to OHM at the price that was set at the time of the bid
/// - During periods of greater demand, the conversion price will increase
/// - During periods of lower demand, the conversion price will decrease
/// - The auction has a minimum price, below which the conversion price will not decrease
/// - The auction has a target amount of convertible OHM to sell per day
/// - When the target is reached, the amount of OHM required to increase the conversion price will decrease, resulting in more rapid price increases (assuming there is demand)
/// - The auction parameters are able to be updated in order to tweak the auction's behaviour
contract ConvertibleDepositAuctioneer is
IConvertibleDepositAuctioneer,
Policy,
PolicyEnabler,
ReentrancyGuard
{
using FullMath for uint256;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.AddressSet;
// ========== CONSTANTS ========== //
/// @notice The role that can perform periodic actions, such as updating the auction parameters
bytes32 public constant ROLE_EMISSION_MANAGER = "cd_emissionmanager";
/// @notice Scale of the OHM token
uint256 internal constant _ohmScale = 1e9;
uint24 public constant ONE_HUNDRED_PERCENT = 100e2;
/// @notice Fixed point scale (WAD)
uint256 internal constant WAD = 1e18;
/// @notice Minimum and maximum allowed tick size base (in WAD)
uint256 internal constant TICK_SIZE_BASE_MIN = 1e18; // 1.0
uint256 internal constant TICK_SIZE_BASE_MAX = 10e18; // 10.0
/// @notice Maximum safe exponent for rpow to prevent overflow
uint256 internal constant MAX_RPOW_EXP = 41;
/// @notice Seconds in one day
uint256 internal constant SECONDS_IN_DAY = 1 days;
/// @notice The length of the enable parameters
uint256 internal constant _ENABLE_PARAMS_LENGTH = 192;
/// @notice The minimum tick size
uint256 internal constant _TICK_SIZE_MINIMUM = 1;
// ========== STRUCTS ========== //
struct BidOutput {
uint256 tickCapacity;
uint256 tickPrice;
uint256 tickSize;
uint256 depositIn;
uint256 ohmOut;
}
struct BidParams {
uint8 depositPeriod;
uint256 depositAmount;
uint256 minOhmOut;
bool wrapPosition;
bool wrapReceipt;
}
struct PendingDepositPeriodChange {
uint8 depositPeriod;
bool enable;
}
// ========== STATE VARIABLES ========== //
/// @notice Whether the deposit period is enabled
mapping(uint8 depositPeriod => bool isDepositPeriodEnabled) internal _depositPeriodsEnabled;
/// @notice The deposit asset
IERC20 internal immutable _DEPOSIT_ASSET;
/// @notice Array of enabled deposit periods
EnumerableSet.UintSet internal _depositPeriods;
/// @notice Previous tick for each deposit period
/// @dev Use `getCurrentTick()` to recalculate and access the latest data
mapping(uint8 depositPeriod => Tick previousTick) internal _depositPeriodPreviousTicks;
/// @notice Address of the Convertible Deposit Facility
ConvertibleDepositFacility public immutable CD_FACILITY;
/// @notice Auction parameters
/// @dev These values should only be set through the `setAuctionParameters()` function
AuctionParameters internal _auctionParameters;
/// @notice The current tick size
uint256 internal _currentTickSize;
/// @notice Auction state for the day
Day internal _dayState;
/// @notice The tick step
/// @dev See `getTickStep()` for more information
uint24 internal _tickStep;
/// @notice The minimum bid amount
/// @dev The minimum bid amount is the minimum amount of deposit asset that can be bid
/// See `getMinimumBid()` for more information
uint256 internal _minimumBid;
/// @notice The base used for exponential tick size reduction (by 1/(base^multiplier)) when the day target is crossed (WAD, 1e18 = 1.0)
uint256 internal _tickSizeBase;
/// @notice The index of the next auction result
uint8 internal _auctionResultsNextIndex;
/// @notice The number of days that auction results are tracked for
uint8 internal _auctionTrackingPeriod;
/// @notice The auction results, where a positive number indicates an over-subscription for the day.
/// @dev The length of this array is equal to the auction tracking period
int256[] internal _auctionResults;
/// @notice Queue of pending deposit period enable/disable changes
PendingDepositPeriodChange[] internal _pendingDepositPeriodChanges;
// ========== SETUP ========== //
constructor(
address kernel_,
address cdFacility_,
address depositAsset_
) Policy(Kernel(kernel_)) {
if (cdFacility_ == address(0))
revert ConvertibleDepositAuctioneer_InvalidParams("cd facility");
if (depositAsset_ == address(0))
revert ConvertibleDepositAuctioneer_InvalidParams("deposit asset");
CD_FACILITY = ConvertibleDepositFacility(cdFacility_);
_DEPOSIT_ASSET = IERC20(depositAsset_);
// PolicyEnabler makes this disabled until enabled
}
/// @inheritdoc Policy
function configureDependencies() external override returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](1);
dependencies[0] = toKeycode("ROLES");
ROLES = ROLESv1(getModuleAddress(dependencies[0]));
}
/// @inheritdoc Policy
function requestPermissions()
external
view
override
returns (Permissions[] memory permissions)
{}
function VERSION() external pure returns (uint8 major, uint8 minor) {
major = 1;
minor = 0;
return (major, minor);
}
// ========== AUCTION ========== //
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function performs the following:
/// - Updates the current tick based on the current state
/// - Determines the amount of OHM that can be purchased for the deposit amount, and the updated tick capacity and price
/// - Updates the day state, if necessary
/// - Creates a convertible deposit position using the deposit amount, the average conversion price and the deposit period
///
/// This function reverts if:
/// - The contract is not active
/// - The auction is disabled
/// - The bid amount is below the minimum bid
/// - Deposits are not enabled for the asset/period/operator
/// - The depositor has not approved the DepositManager to spend the deposit asset
/// - The depositor has an insufficient balance of the deposit asset
/// - The calculated amount of OHM out is 0
/// - The calculated amount of OHM out is < minOhmOut_
function bid(
uint8 depositPeriod_,
uint256 depositAmount_,
uint256 minOhmOut_,
bool wrapPosition_,
bool wrapReceipt_
)
external
override
nonReentrant
onlyEnabled
onlyDepositPeriodEnabled(depositPeriod_)
returns (uint256, uint256, uint256, uint256)
{
return
_bid(
BidParams({
depositPeriod: depositPeriod_,
depositAmount: depositAmount_,
minOhmOut: minOhmOut_,
wrapPosition: wrapPosition_,
wrapReceipt: wrapReceipt_
})
);
}
/// @notice Internal function to submit an auction bid on the given deposit asset and period
/// @dev This function expects the calling function to have already validated the contract state and deposit asset and period
function _bid(BidParams memory params) internal returns (uint256, uint256, uint256, uint256) {
// If target is 0, auction is disabled - reject all bids
if (_auctionParameters.target == 0) {
revert ConvertibleDepositAuctioneer_ConvertedAmountZero();
}
// Check minimum bid requirement
if (params.depositAmount < _minimumBid) {
revert ConvertibleDepositAuctioneer_BidBelowMinimum(params.depositAmount, _minimumBid);
}
uint256 ohmOut;
uint256 depositIn;
{
// Get the current tick for the deposit asset and period
Tick memory updatedTick = _getCurrentTick(params.depositPeriod);
// Get bid results
BidOutput memory output = _previewBid(params.depositAmount, updatedTick);
// Reject if the OHM out is 0
if (output.ohmOut == 0) revert ConvertibleDepositAuctioneer_ConvertedAmountZero();
// Reject if the OHM out is below the minimum OHM out
if (output.ohmOut < params.minOhmOut)
revert ConvertibleDepositAuctioneer_ConvertedAmountSlippage(
output.ohmOut,
params.minOhmOut
);
// Update state
_dayState.convertible += output.ohmOut;
// Update current tick
updatedTick.price = output.tickPrice;
updatedTick.capacity = output.tickCapacity;
updatedTick.lastUpdate = uint48(block.timestamp);
_depositPeriodPreviousTicks[params.depositPeriod] = updatedTick;
// Update the current tick size
if (output.tickSize != _currentTickSize) {
if (_depositPeriods.length() > 1) {
// Before updating the global tick size, ensure that all other periods are updated
// This ensures that if the tick size changes, the change will not be
// applied retroactively
// Only required if there are other deposit periods than the one being bid on
_updateCurrentTicks(params.depositPeriod);
}
_currentTickSize = output.tickSize;
}
// Set values for the rest of the function
ohmOut = output.ohmOut;
depositIn = output.depositIn;
}
// Calculate average price based on the total deposit and ohmOut
// This is the number of deposit tokens per OHM token
// We round up to be conservative
// Create the receipt tokens and position
(uint256 positionId, uint256 receiptTokenId, uint256 actualAmount) = CD_FACILITY
.createPosition(
IConvertibleDepositFacility.CreatePositionParams({
asset: _DEPOSIT_ASSET,
periodMonths: params.depositPeriod,
depositor: msg.sender,
amount: depositIn,
conversionPrice: depositIn.mulDivUp(_ohmScale, ohmOut), // Assets per OHM, deposit token scale
wrapPosition: params.wrapPosition,
wrapReceipt: params.wrapReceipt
})
);
// Emit event
emit Bid(
msg.sender,
address(_DEPOSIT_ASSET),
params.depositPeriod,
depositIn,
ohmOut,
positionId
);
return (ohmOut, positionId, receiptTokenId, actualAmount);
}
/// @notice Internal function to preview the quantity of OHM tokens that can be purchased for a given deposit amount
/// @dev This function performs the following:
/// - Cycles through ticks until the deposit is fully converted
/// - If the current tick has enough capacity, it will be used
/// - If the current tick does not have enough capacity, the remaining capacity will be used. The current tick will then shift to the next tick, resulting in the capacity being filled to the tick size, and the price being multiplied by the tick step.
///
/// Notes:
/// - This function assumes that the auction is active (i.e. the target is non-zero) and the tick size is non-zero
/// - The function returns the updated tick capacity and price after the bid
/// - If the capacity of a tick is depleted (but does not cross into the next tick), the current tick will be shifted to the next one. This ensures that `getCurrentTick()` will not return a tick that has been depleted.
///
/// @param deposit_ The amount of deposit to be bid
/// @return output The output of the bid
function _previewBid(
uint256 deposit_,
Tick memory tick_
) internal view returns (BidOutput memory output) {
uint256 remainingDeposit = deposit_;
output.tickCapacity = tick_.capacity;
output.tickPrice = tick_.price;
output.tickSize = _currentTickSize;
// Load, as this will be used within the loop
AuctionParameters memory auctionParams = _auctionParameters;
Day memory dayState = _dayState;
// Cycle through the ticks until the deposit is fully converted
while (remainingDeposit > 0) {
uint256 depositAmount = remainingDeposit;
uint256 convertibleAmount = _getConvertedDeposit(remainingDeposit, output.tickPrice);
// No point in continuing if the converted amount is 0
if (convertibleAmount == 0) break;
// Determine if we're crossing a threshold or depleting capacity
uint256 baseConvertible = dayState.convertible + output.ohmOut;
uint256 ohmUntilNextThreshold = _getOhmUntilNextThreshold(
baseConvertible,
auctionParams.target
);
bool willCrossThreshold = (ohmUntilNextThreshold > 0 &&
ohmUntilNextThreshold <= convertibleAmount);
bool willDepleteTick = (output.tickCapacity <= convertibleAmount);
// If either condition triggers, we need to perform a tick transition
if (willCrossThreshold || willDepleteTick) {
// Use whichever limit is more restrictive
if (willCrossThreshold && ohmUntilNextThreshold < output.tickCapacity) {
// Day target threshold is the limiting factor
convertibleAmount = ohmUntilNextThreshold;
} else {
// Tick capacity is the limiting factor
convertibleAmount = output.tickCapacity;
}
// Calculate deposit amount for this limited chunk
// Round in favour of the protocol
depositAmount = convertibleAmount.mulDivUp(output.tickPrice, _ohmScale);
// Trigger tick transition: increase price and recalculate tick size
output.tickPrice = _getNewTickPrice(output.tickPrice, _tickStep);
output.tickSize = _getNewTickSize(
baseConvertible + convertibleAmount,
auctionParams
);
output.tickCapacity = output.tickSize;
}
// Otherwise, the tick has enough capacity and we're not crossing threshold
else {
output.tickCapacity -= convertibleAmount;
}
// Record updates to the deposit and OHM
if (depositAmount <= remainingDeposit) {
remainingDeposit -= depositAmount;
} else {
remainingDeposit = 0;
}
output.ohmOut += convertibleAmount;
}
output.depositIn = deposit_ - remainingDeposit;
return output;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function previewBid(
uint8 depositPeriod_,
uint256 bidAmount_
)
external
view
override
onlyEnabled
onlyDepositPeriodEnabled(depositPeriod_)
returns (uint256 ohmOut)
{
// If target is 0, auction is disabled - return 0
if (_auctionParameters.target == 0) {
return 0;
}
// If bid amount is below minimum, return 0
if (bidAmount_ < _minimumBid) {
return 0;
}
// Get the updated tick based on the current state
Tick memory currentTick = _getCurrentTick(depositPeriod_);
// Preview the bid results
BidOutput memory output = _previewBid(bidAmount_, currentTick);
ohmOut = output.ohmOut;
return ohmOut;
}
// ========== VIEW FUNCTIONS ========== //
/// @notice Internal function to preview the quantity of OHM tokens that can be purchased for a given deposit amount
/// @dev This function does not take into account the capacity of the current tick
///
/// @param deposit_ The amount of deposit to be converted
/// @param price_ The price of the deposit in OHM
/// @return convertibleAmount The quantity of OHM tokens that can be purchased
function _getConvertedDeposit(
uint256 deposit_,
uint256 price_
) internal pure returns (uint256 convertibleAmount) {
// As price represents the number of bid tokens per OHM, we can convert the deposit to OHM by dividing by the price and adjusting for the decimal scale
convertibleAmount = deposit_.mulDiv(_ohmScale, price_);
return convertibleAmount;
}
/// @notice Internal function to preview the new price of the current tick after applying the tick step
/// @dev This function does not take into account the capacity of the current tick
///
/// @param currentPrice_ The current price of the tick in terms of the bid token
/// @param tickStep_ The step size of the tick
/// @return newPrice The new price of the tick
function _getNewTickPrice(
uint256 currentPrice_,
uint256 tickStep_
) internal pure returns (uint256 newPrice) {
newPrice = currentPrice_.mulDivUp(tickStep_, ONE_HUNDRED_PERCENT);
return newPrice;
}
/// @notice Internal function to calculate the new tick size based on the amount of OHM that has been converted in the current day
/// @dev This implements exponential tick size reduction (by 1/(base^multiplier)) for each multiple of the day target that is reached
/// If the new tick size is 0 or a calculation would result in an overflow, the tick size is set to the minimum
///
/// @param ohmOut_ The amount of OHM that has been converted in the current day
/// @return newTickSize The new tick size
function _getNewTickSize(
uint256 ohmOut_,
AuctionParameters memory auctionParams_
) internal view returns (uint256 newTickSize) {
// If the day target is zero, the tick size is always the configured size (which should be 0 to disable auction)
if (auctionParams_.target == 0) {
return auctionParams_.tickSize;
}
// Calculate the multiplier
uint256 multiplier = ohmOut_ / auctionParams_.target;
// If the day target has not been met, the tick size remains the standard
if (multiplier == 0) {
newTickSize = auctionParams_.tickSize;
return newTickSize;
}
// If the multiplier would result in an overflow in rpow, return minimum tick size
// For base = 1e18, rpow always returns 1e18 regardless of exponent, so overflow is impossible
if (_tickSizeBase != TICK_SIZE_BASE_MIN && multiplier > MAX_RPOW_EXP) {
return _TICK_SIZE_MINIMUM;
}
// Otherwise, the tick size is reduced by a factor of (base^multiplier) (WAD base)
// divisor = _tickSizeBase^multiplier (scaled by 1e18 via rpow)
uint256 divisor = FixedPointMathLib.rpow(_tickSizeBase, multiplier, WAD);
if (divisor == 0) return _TICK_SIZE_MINIMUM;
// newTickSize = tickSize * WAD / divisor (round down)
newTickSize = auctionParams_.tickSize.mulDiv(WAD, divisor);
// This can round down to zero (which would cause problems with calculations), so provide a fallback
if (newTickSize == 0) return _TICK_SIZE_MINIMUM;
return newTickSize;
}
/// @notice Internal function to calculate the amount of OHM remaining until the next day target threshold is reached
///
/// @param currentConvertible_ The current cumulative amount of OHM that has been converted
/// @param target_ The day target
/// @return ohmUntilThreshold The amount of OHM remaining until the next threshold
function _getOhmUntilNextThreshold(
uint256 currentConvertible_,
uint256 target_
) internal pure returns (uint256 ohmUntilThreshold) {
if (target_ == 0) return type(uint256).max;
uint256 currentMultiplier = currentConvertible_ / target_;
uint256 nextThreshold = (currentMultiplier + 1) * target_;
return nextThreshold - currentConvertible_;
}
function _getCurrentTick(uint8 depositPeriod_) internal view returns (Tick memory tick) {
Tick memory previousTick = _depositPeriodPreviousTicks[depositPeriod_];
// If the target is 0, auction is disabled - return previous tick without decay
if (_auctionParameters.target == 0) {
return previousTick;
}
// Find amount of time passed and new capacity to add
uint256 newCapacity;
{
uint256 timePassed = block.timestamp - previousTick.lastUpdate;
// The capacity to add is the day target multiplied by the proportion of time passed in a day
// It is also adjusted by the number of deposit periods that are enabled, otherwise each auction would have too much capacity added
uint256 capacityToAdd = (_auctionParameters.target * timePassed) /
SECONDS_IN_DAY /
_depositPeriods.length();
tick = previousTick;
newCapacity = tick.capacity + capacityToAdd;
}
// Iterate over the ticks until the capacity is within the tick size
// This is the opposite of what happens in the bid function
// It uses the standard tick size (unaffected by the achievement of the day target),
// otherwise the tick price would decay quickly
uint256 tickSize = _auctionParameters.tickSize;
while (newCapacity > tickSize) {
// Reduce the capacity by the tick size
newCapacity -= tickSize;
// Adjust the tick price by the tick step, in the opposite direction to the bid function
tick.price = tick.price.mulDivUp(ONE_HUNDRED_PERCENT, _tickStep);
// Tick price does not go below the minimum
if (tick.price < _auctionParameters.minPrice) {
tick.price = _auctionParameters.minPrice;
break;
}
}
// Set the capacity, but ensure it doesn't exceed the current tick size
// (which may have been reduced if the day target was met)
tick.capacity = newCapacity > _currentTickSize ? _currentTickSize : newCapacity;
return tick;
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function calculates the tick at the current time.
///
/// It uses the following approach:
/// - Calculate the added capacity based on the time passed since the last bid, and add it to the current capacity to get the new capacity
/// - Until the new capacity is <= to the standard tick size, reduce the capacity by the standard tick size and reduce the price by the tick step
/// - If the calculated price is ever lower than the minimum price, the new price is set to the minimum price and the capacity is set to the standard tick size
///
/// Notes:
/// - If the target is 0, the price will not decay and the capacity will not change. It will only decay when a target is set again to a non-zero value.
///
/// This function reverts if:
/// - The deposit asset and period are not enabled
function getCurrentTick(
uint8 depositPeriod_
) external view onlyDepositPeriodEnabled(depositPeriod_) returns (Tick memory tick) {
return _getCurrentTick(depositPeriod_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function returns the previous tick for the deposit period
/// If the deposit period is not configured, all values will be 0
function getPreviousTick(uint8 depositPeriod_) public view override returns (Tick memory tick) {
return _depositPeriodPreviousTicks[depositPeriod_];
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getCurrentTickSize() external view override returns (uint256) {
return _currentTickSize;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getAuctionParameters() external view override returns (AuctionParameters memory) {
return _auctionParameters;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function isAuctionActive() external view override returns (bool) {
return _auctionParameters.target > 0;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getDayState() external view override returns (Day memory) {
return _dayState;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getTickStep() external view override returns (uint24) {
return _tickStep;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getMinimumBid() external view override returns (uint256) {
return _minimumBid;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getAuctionTrackingPeriod() external view override returns (uint8) {
return _auctionTrackingPeriod;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getAuctionResultsNextIndex() external view override returns (uint8) {
return _auctionResultsNextIndex;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getAuctionResults() external view override returns (int256[] memory) {
return _auctionResults;
}
// ========== PENDING CHANGES HELPERS ========== //
/// @notice Validates that the requested action would not result in the same effective state, preventing redundant queue operations
/// @param depositPeriod_ The deposit period to check
/// @param enable_ Whether the requested operation is to enable (true) or disable (false)
function _validateNoDuplicatePendingChange(uint8 depositPeriod_, bool enable_) internal view {
// Get the effective state considering pending changes
bool effectiveState = _getEffectiveDepositPeriodState(depositPeriod_);
// Check if the requested change would result in the same state
if (effectiveState == enable_) {
revert ConvertibleDepositAuctioneer_DepositPeriodInvalidState(
address(_DEPOSIT_ASSET),
depositPeriod_,
effectiveState
);
}
}
/// @notice Gets the effective state of a deposit period considering pending changes
/// @param depositPeriod_ The deposit period to check
/// @return effectiveState The effective enabled state (current state + pending changes)
function _getEffectiveDepositPeriodState(
uint8 depositPeriod_
) internal view returns (bool effectiveState) {
// Start with current state
effectiveState = _depositPeriodsEnabled[depositPeriod_];
// Later entries take precedence; scan from the end and short-circuit
for (uint256 i = _pendingDepositPeriodChanges.length; i > 0; ) {
unchecked {
i--;
}
PendingDepositPeriodChange memory c = _pendingDepositPeriodChanges[i];
if (c.depositPeriod == depositPeriod_) {
effectiveState = c.enable;
break;
}
}
return effectiveState;
}
/// @notice Processes all pending deposit period changes
/// @param tickSize_ The tick size to initialize new periods with
/// @param minPrice_ The minimum price to initialize new periods with
function _processPendingDepositPeriodChanges(uint256 tickSize_, uint256 minPrice_) internal {
uint256 len = _pendingDepositPeriodChanges.length;
for (uint256 i = 0; i < len; ) {
PendingDepositPeriodChange memory change = _pendingDepositPeriodChanges[i];
if (change.enable) {
_enableDepositPeriod(change.depositPeriod, tickSize_, minPrice_);
} else {
_disableDepositPeriod(change.depositPeriod);
}
unchecked {
++i;
}
}
// Clear the pending changes queue
delete _pendingDepositPeriodChanges;
}
/// @notice Internal function to actually enable a deposit period
/// @param depositPeriod_ The deposit period to enable
/// @param tickSize_ The tick size to initialize with
/// @param minPrice_ The minimum price to initialize with
function _enableDepositPeriod(
uint8 depositPeriod_,
uint256 tickSize_,
uint256 minPrice_
) internal {
// Skip if already enabled (shouldn't happen due to validation, but defensive)
if (_depositPeriodsEnabled[depositPeriod_]) {
return;
}
// Enable the deposit period
_depositPeriodsEnabled[depositPeriod_] = true;
// Add the deposit period to the array
// No check necessary, as EnumerableSet will handle duplicates
_depositPeriods.add(depositPeriod_);
// Initialize the tick with the new auction parameters
_depositPeriodPreviousTicks[depositPeriod_] = Tick({
price: minPrice_,
capacity: tickSize_,
lastUpdate: uint48(block.timestamp)
});
// Emit event for actual enabling
emit DepositPeriodEnabled(address(_DEPOSIT_ASSET), depositPeriod_);
}
/// @notice Internal function to actually disable a deposit period
/// @param depositPeriod_ The deposit period to disable
function _disableDepositPeriod(uint8 depositPeriod_) internal {
// Skip if already disabled (shouldn't happen due to validation, but defensive)
if (!_depositPeriodsEnabled[depositPeriod_]) {
return;
}
// Disable the deposit period
_depositPeriodsEnabled[depositPeriod_] = false;
// Remove the deposit period from the array
// No check necessary, as EnumerableSet will handle non-existence
_depositPeriods.remove(depositPeriod_);
// Remove the tick
delete _depositPeriodPreviousTicks[depositPeriod_];
// Emit event for actual disabling
emit DepositPeriodDisabled(address(_DEPOSIT_ASSET), depositPeriod_);
}
// ========== ASSET CONFIGURATION ========== //
/// @inheritdoc IConvertibleDepositAuctioneer
function getDepositAsset() external view override returns (IERC20) {
return _DEPOSIT_ASSET;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getDepositPeriods() external view override returns (uint8[] memory) {
uint256 length = _depositPeriods.length();
uint8[] memory periods = new uint8[](length);
for (uint256 i = 0; i < length; i++) {
periods[i] = uint8(_depositPeriods.at(i));
}
return periods;
}
/// @inheritdoc IConvertibleDepositAuctioneer
function isDepositPeriodEnabled(
uint8 depositPeriod_
) public view override returns (bool isEnabled, bool isPendingEnabled) {
isEnabled = _depositPeriodsEnabled[depositPeriod_];
isPendingEnabled = _getEffectiveDepositPeriodState(depositPeriod_);
return (isEnabled, isPendingEnabled);
}
function _onlyDepositPeriodEnabled(uint8 depositPeriod_) internal view {
(bool isEnabled, ) = isDepositPeriodEnabled(depositPeriod_);
if (!isEnabled) {
revert ConvertibleDepositAuctioneer_DepositPeriodNotEnabled(
address(_DEPOSIT_ASSET),
depositPeriod_
);
}
}
/// @notice Modifier to check if a deposit period is enabled
modifier onlyDepositPeriodEnabled(uint8 depositPeriod_) {
_onlyDepositPeriodEnabled(depositPeriod_);
_;
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev Notes:
/// - Enabling a deposit period will queue the change to be processed at the next setAuctionParameters call
/// - Can be called while the contract is disabled (changes will be processed when contract is enabled)
/// - The deposit period will reset the minimum price and tick size to the standard values when actually enabled
///
/// This function will revert if:
/// - The caller is not a manager or admin
/// - The deposit period is 0
/// - The effective state would result in enabling an already enabled period
function enableDepositPeriod(uint8 depositPeriod_) external override onlyManagerOrAdminRole {
// Validate that the deposit period is not 0
if (depositPeriod_ == 0)
revert ConvertibleDepositAuctioneer_InvalidParams("deposit period");
// Validate that this wouldn't result in a duplicate state
_validateNoDuplicatePendingChange(depositPeriod_, true);
// Add to pending changes queue
_pendingDepositPeriodChanges.push(
PendingDepositPeriodChange({depositPeriod: depositPeriod_, enable: true})
);
// Emit event to indicate the change has been queued
emit DepositPeriodEnableQueued(address(_DEPOSIT_ASSET), depositPeriod_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev Notes:
/// - Disabling a deposit period will queue the change to be processed at the next setAuctionParameters call
/// - Can be called while the contract is disabled (changes will be processed when contract is enabled)
///
/// This function will revert if:
/// - The caller is not a manager or admin
/// - The deposit period is 0
/// - The effective state would result in disabling an already disabled period
function disableDepositPeriod(uint8 depositPeriod_) external override onlyManagerOrAdminRole {
// Validate that the deposit period is not 0
if (depositPeriod_ == 0)
revert ConvertibleDepositAuctioneer_InvalidParams("deposit period");
// Validate that this wouldn't result in a duplicate state
_validateNoDuplicatePendingChange(depositPeriod_, false);
// Add to pending changes queue
_pendingDepositPeriodChanges.push(
PendingDepositPeriodChange({depositPeriod: depositPeriod_, enable: false})
);
// Emit event to indicate the change has been queued
emit DepositPeriodDisableQueued(address(_DEPOSIT_ASSET), depositPeriod_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getDepositPeriodsCount() external view override returns (uint256) {
return _depositPeriods.length();
}
/// @notice Gets the list of pending deposit period changes, from first to last
function getPendingDepositPeriodChanges()
external
view
returns (PendingDepositPeriodChange[] memory)
{
return _pendingDepositPeriodChanges;
}
// ========== ADMIN FUNCTIONS ========== //
function _setAuctionParameters(uint256 target_, uint256 tickSize_, uint256 minPrice_) internal {
// The target can be zero (disables auction)
// When target is zero, tick size and min price are irrelevant since auction is disabled
// Tick size must be non-zero when target is non-zero
if (target_ > 0 && tickSize_ == 0)
revert ConvertibleDepositAuctioneer_InvalidParams("tick size");
// Min price must be non-zero when target is non-zero (can be zero when auction is disabled)
if (target_ > 0 && minPrice_ == 0)
revert ConvertibleDepositAuctioneer_InvalidParams("min price");
// If the target is non-zero, the tick size must be <= target
if (target_ > 0 && tickSize_ > target_)
revert ConvertibleDepositAuctioneer_InvalidParams("tick size");
_auctionParameters = AuctionParameters({
target: target_,
tickSize: tickSize_,
minPrice: minPrice_
});
// Emit event
emit AuctionParametersUpdated(address(_DEPOSIT_ASSET), target_, tickSize_, minPrice_);
}
function _storeAuctionResults(uint256 previousTarget_) internal {
// Skip if inactive
if (!isEnabled) return;
// If the next index is 0, reset the results before inserting
// This ensures that the previous results are available for 24 hours
if (_auctionResultsNextIndex == 0) {
_auctionResults = new int256[](_auctionTrackingPeriod);
}
// Store the auction results
// Negative values will indicate under-selling
/// forge-lint: disable-start(unsafe-typecast)
_auctionResults[_auctionResultsNextIndex] =
int256(_dayState.convertible) -
int256(previousTarget_);
/// forge-lint: disable-end(unsafe-typecast)
// Emit event
emit AuctionResult(
address(_DEPOSIT_ASSET),
_dayState.convertible,
previousTarget_,
_auctionResultsNextIndex
);
// Increment the index (or loop around)
_auctionResultsNextIndex++;
// Loop around if necessary
if (_auctionResultsNextIndex >= _auctionTrackingPeriod) {
_auctionResultsNextIndex = 0;
}
// Reset the day state
_dayState = Day({initTimestamp: uint48(block.timestamp), convertible: 0});
}
/// @notice Sets tick parameters for all enabled deposit periods
///
/// @param tickSize_ If the new tick size is less than a tick's capacity (or `enforceCapacity_` is true), the tick capacity will be set to this
/// @param minPrice_ If the new minimum price is greater than a tick's price (or `enforceMinPrice_` is true), the tick price will be set to this
/// @param enforceCapacity_ If true, will set the capacity of each enabled deposit period to the value of `tickSize_`
/// @param enforceMinPrice_ If true, will set the price of each enabled deposit period to the value of `minPrice_`
/// @param setLastUpdate_ If true, will set the tick's last update to the current timestamp
function _setNewTickParameters(
uint256 tickSize_,
uint256 minPrice_,
bool enforceCapacity_,
bool enforceMinPrice_,
bool setLastUpdate_
) internal {
// Iterate over periods
uint256 periodLength = _depositPeriods.length();
for (uint256 j = 0; j < periodLength; j++) {
uint8 period = uint8(_depositPeriods.at(j));
// Skip if the deposit period is not enabled
if (!_depositPeriodsEnabled[period]) continue;
// Get the previous tick
Tick storage previousTick = _depositPeriodPreviousTicks[period];
// Ensure that the tick capacity is not larger than the new tick size
// Otherwise, excess OHM will be converted
if (tickSize_ < previousTick.capacity || enforceCapacity_) {
previousTick.capacity = tickSize_;
}
// Ensure that the minimum price is enforced
// Otherwise, OHM will be converted at a price lower than the minimum
if (minPrice_ > previousTick.price || enforceMinPrice_) {
previousTick.price = minPrice_;
}
// Set the last update
if (setLastUpdate_) {
previousTick.lastUpdate = uint48(block.timestamp);
}
}
// Set the tick size
// This has the effect of resetting the tick size to the default
// The tick size may have been adjusted for the previous day if the target was met
_currentTickSize = tickSize_;
}
/// @notice Takes a snapshot of the current tick values for enabled deposit periods
///
/// @param excludedDepositPeriod_ The deposit period that should be excluded from updates. Provide 0 to not exclude (since 0 is an invalid deposit period).
function _updateCurrentTicks(uint8 excludedDepositPeriod_) internal {
// Iterate over periods
uint256 periodLength = _depositPeriods.length();
for (uint256 i; i < periodLength; i++) {
uint8 period = uint8(_depositPeriods.at(i));
// Skip if the deposit period is excluded
if (period == excludedDepositPeriod_) continue;
// Skip if the deposit period is not enabled
if (!_depositPeriodsEnabled[period]) continue;
// Get the current tick for the deposit asset and period
Tick memory updatedTick = _getCurrentTick(period);
updatedTick.lastUpdate = uint48(block.timestamp);
// Update the current tick for the deposit period
_depositPeriodPreviousTicks[period] = updatedTick;
}
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function assumes that the the caller is only calling once per period (day), as the contract does not track epochs or timestamps.
///
/// This function performs the following:
/// - Performs validation of the inputs
/// - Captures the current tick state for all enabled deposit periods
/// - Stores the auction results for the previous period
/// - Sets the auction parameters
/// - Sets the tick parameters for all enabled deposit periods
/// - Processes any pending deposit period changes
///
/// This function reverts if:
/// - The caller does not have the ROLE_EMISSION_MANAGER role
/// - The new tick size is 0
/// - The new min price is 0
function setAuctionParameters(
uint256 target_,
uint256 tickSize_,
uint256 minPrice_
) external override onlyRole(ROLE_EMISSION_MANAGER) {
uint256 previousTarget = _auctionParameters.target;
// Update tick state for enabled assets and periods
// This prevents retroactive application of new parameters
_updateCurrentTicks(0);
// Store the auction results based on the previous state, before any changes
_storeAuctionResults(previousTarget);
// Update global state
_setAuctionParameters(target_, tickSize_, minPrice_);
// The following can be done even if the contract is not active nor initialized, since activating/initializing will set the tick capacity and price
// Ensure all existing ticks are updated with the new parameters
_setNewTickParameters(tickSize_, minPrice_, false, false, false);
// Process pending deposit period changes with the new parameters
// This must be called after _setNewTickParameters to ensure new periods get correct parameters
_processPendingDepositPeriodChanges(tickSize_, minPrice_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function will revert if:
/// - The caller does not have the ROLE_ADMIN role
/// - The new tick step is < 100e2
///
/// @param newStep_ The new tick step
function setTickStep(uint24 newStep_) public override onlyManagerOrAdminRole {
// Value must be more than 100e2
if (newStep_ < ONE_HUNDRED_PERCENT)
revert ConvertibleDepositAuctioneer_InvalidParams("tick step");
// Capture the tick state, otherwise the tick step change will apply retroactively
_updateCurrentTicks(0);
// Set the tick step
_tickStep = newStep_;
// Emit event
emit TickStepUpdated(address(_DEPOSIT_ASSET), newStep_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
function getTickSizeBase() external view override returns (uint256) {
return _tickSizeBase;
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function will revert if:
/// - The caller does not have the ROLE_ADMIN or ROLE_MANAGER role
/// - The new tick size base is not within the bounds (1e18 ≤ base ≤ 10e18)
///
/// @param newBase_ The new tick size base
function setTickSizeBase(uint256 newBase_) public override onlyManagerOrAdminRole {
// Bounds: 1e18 ≤ base ≤ 10e18
if (newBase_ < TICK_SIZE_BASE_MIN || newBase_ > TICK_SIZE_BASE_MAX)
revert ConvertibleDepositAuctioneer_InvalidParams("tick size base");
// Do not snapshot/update current ticks; applies during bidding only
_tickSizeBase = newBase_;
emit TickSizeBaseUpdated(address(_DEPOSIT_ASSET), newBase_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev Notes:
/// - Calling this function will erase the previous auction results, which in turn may affect the bond markets created to sell under-sold OHM capacity
///
/// This function will revert if:
/// - The caller does not have the ROLE_ADMIN role
/// - The new auction tracking period is 0
///
/// @param days_ The new auction tracking period
function setAuctionTrackingPeriod(uint8 days_) public override onlyManagerOrAdminRole {
// Value must be non-zero
if (days_ == 0)
revert ConvertibleDepositAuctioneer_InvalidParams("auction tracking period");
_auctionTrackingPeriod = days_;
// Reset the auction results and index and set to the new length
_auctionResults = new int256[](days_);
_auctionResultsNextIndex = 0;
// Emit event
emit AuctionTrackingPeriodUpdated(address(_DEPOSIT_ASSET), days_);
}
/// @inheritdoc IConvertibleDepositAuctioneer
/// @dev This function will revert if:
/// - The caller does not have the ROLE_ADMIN or ROLE_MANAGER role
///
/// @param minimumBid_ The new minimum bid amount
function setMinimumBid(uint256 minimumBid_) external override onlyManagerOrAdminRole {
_minimumBid = minimumBid_;
// Emit event
emit MinimumBidUpdated(address(_DEPOSIT_ASSET), minimumBid_);
}
// ========== ACTIVATION/DEACTIVATION ========== //
/// @inheritdoc PolicyEnabler
/// @dev This function will revert if:
/// - The enable data is not the correct length
/// - The enable data is not an encoded `EnableParams` struct
/// - The auction parameters are invalid
/// - The tick step is invalid
/// - The auction tracking period is invalid
///
/// This function performs the following:
/// - Sets the auction parameters
/// - Sets the tick step
/// - Sets the auction tracking period
/// - Ensures all existing ticks have the current parameters
/// - Processes any pending deposit period changes with the new parameters (including any that were pending prior to disabling)
/// - Resets the day state
/// - Resets the auction results
function _enable(bytes calldata enableData_) internal override {
if (enableData_.length != _ENABLE_PARAMS_LENGTH)
revert ConvertibleDepositAuctioneer_InvalidParams("enable data");
// Decode the enable data
EnableParams memory params = abi.decode(enableData_, (EnableParams));
// Set the auction parameters
_setAuctionParameters(params.target, params.tickSize, params.minPrice);
// Set the tick step
// OK to call this as the caller will have admin role
setTickStep(params.tickStep);
// Set the auction tracking period
setAuctionTrackingPeriod(params.auctionTrackingPeriod);
// Set the tick size base
// OK to call this as the caller will have admin role
setTickSizeBase(params.tickSizeBase);
// Ensure all existing ticks have the current parameters
// Also set the lastUpdate to the current block timestamp
// Otherwise, getCurrentTick() will calculate a long period of time having passed
_setNewTickParameters(params.tickSize, params.minPrice, true, true, true);
// Process any pending deposit period changes with the new parameters
// This enables queued changes to be processed immediately when contract is enabled
_processPendingDepositPeriodChanges(params.tickSize, params.minPrice);
// Reset the day state
_dayState = Day({initTimestamp: uint48(block.timestamp), convertible: 0});
// Reset the auction results
_auctionResults = new int256[](_auctionTrackingPeriod);
_auctionResultsNextIndex = 0;
}
}
/// forge-lint: disable-end(mixed-case-function, screaming-snake-case-const)
"
},
"dependencies/solmate-6.2.0/src/utils/ReentrancyGuard.sol": {
"content": "// 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;
}
}
"
},
"dependencies/solmate-6.2.0/src/utils/FixedPointMathLib.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
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.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, 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(y, 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
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
"
},
"src/libraries/FullMath.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// 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 = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
Submitted on: 2025-11-07 16:15:58
Comments
Log in to comment.
No comments yet.