EmissionManager

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/EmissionManager.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
/// forge-lint: disable-start(mixed-case-function, mixed-case-variable, screaming-snake-case-immutable)
pragma solidity >=0.8.15;

// Libraries
import {ERC20} from "@solmate-6.2.0/tokens/ERC20.sol";
import {ERC4626} from "@solmate-6.2.0/mixins/ERC4626.sol";
import {TransferHelper} from "src/libraries/TransferHelper.sol";
import {FullMath} from "src/libraries/FullMath.sol";

// Interfaces
import {IBondSDA} from "src/interfaces/IBondSDA.sol";
import {IgOHM} from "src/interfaces/IgOHM.sol";
import {IEmissionManager} from "src/policies/interfaces/IEmissionManager.sol";
import {IConvertibleDepositAuctioneer} from "src/policies/interfaces/deposits/IConvertibleDepositAuctioneer.sol";
import {IGenericClearinghouse} from "src/policies/interfaces/IGenericClearinghouse.sol";
import {IPeriodicTask} from "src/interfaces/IPeriodicTask.sol";
import {IERC165} from "@openzeppelin-4.8.0/interfaces/IERC165.sol";
import {IEnabler} from "src/periphery/interfaces/IEnabler.sol";

// Bophades
import {Kernel, Keycode, Permissions, Policy, toKeycode} from "src/Kernel.sol";
import {ROLESv1} from "src/modules/ROLES/OlympusRoles.sol";
import {TRSRYv1} from "src/modules/TRSRY/TRSRY.v1.sol";
import {PRICEv1} from "src/modules/PRICE/PRICE.v1.sol";
import {MINTRv1} from "src/modules/MINTR/MINTR.v1.sol";
import {CHREGv1} from "src/modules/CHREG/CHREG.v1.sol";
import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol";

// solhint-disable max-states-count
contract EmissionManager is IEmissionManager, IPeriodicTask, Policy, PolicyEnabler {
    using FullMath for uint256;
    using TransferHelper for ERC20;

    // ========== CONSTANTS ========== //

    uint256 internal constant ONE_HUNDRED_PERCENT = 1e18;

    uint256 internal constant MAX_BOND_MARKET_CAPACITY_SCALAR = 2e18;

    /// @notice The role assigned to the Heart contract.
    ///         This enables the Heart contract to call specific functions on this contract.
    bytes32 public constant ROLE_HEART = "heart";

    /// @notice The length of the `EnableParams` struct in bytes
    uint256 internal constant ENABLE_PARAMS_LENGTH = 224;

    // ========== STATE VARIABLES ========== //

    /// @notice active base emissions rate change information
    /// @dev active until daysLeft is 0
    BaseRateChange public rateChange;

    // Modules
    TRSRYv1 public TRSRY;
    PRICEv1 public PRICE;
    MINTRv1 public MINTR;
    CHREGv1 public CHREG;

    // Tokens
    // solhint-disable immutable-vars-naming
    ERC20 public immutable ohm;
    IgOHM public immutable gohm;
    ERC20 public immutable reserve;
    ERC4626 public immutable sReserve;
    // solhint-enable immutable-vars-naming

    // External contracts
    IBondSDA public bondAuctioneer;
    address public teller;
    IConvertibleDepositAuctioneer public cdAuctioneer;

    // Manager variables

    /// @notice The base emission rate, in OHM scale.
    /// @dev    e.g. 2e5 = 0.02%
    uint256 public baseEmissionRate;

    /// @notice The minimum premium for bond markets created by the manager, in terms of ONE_HUNDRED_PERCENT.
    /// @dev    A minimum premium of 1e18 would require the market price to be 100% above the backing price (i.e. double).
    uint256 public minimumPremium;

    /// @notice The vesting period for bond markets created by the manager, in seconds.
    /// @dev    Initialized at 0, which means no vesting.
    uint48 public vestingPeriod;

    /// @notice The backed price of OHM, in reserve scale.
    uint256 public backing;

    /// @notice Used to track the number of beats that have occurred.
    uint8 public beatCounter;

    /// @notice The ID of the active bond market (or 0)
    uint256 public activeMarketId;

    /// @notice The fixed tick size for CD auctions, in OHM scale (9 decimals)
    uint256 public tickSize;

    /// @notice The multiplier applied to the price, in terms of ONE_HUNDRED_PERCENT
    /// @dev    The value must be greater than or equal to ONE_HUNDRED_PERCENT (100%)
    uint256 public minPriceScalar;

    /// @notice The multiplier applied to bond market capacity from auction remainders, in terms of ONE_HUNDRED_PERCENT
    /// @dev    The value must be between 0 and MAX_BOND_MARKET_CAPACITY_SCALAR (0-200%)
    uint256 public bondMarketCapacityScalar;

    uint8 internal _oracleDecimals;
    // solhint-disable immutable-vars-naming
    uint8 internal immutable _ohmDecimals;
    uint8 internal immutable _gohmDecimals;
    uint8 internal immutable _reserveDecimals;
    // solhint-enable immutable-vars-naming

    /// @notice timestamp of last shutdown
    uint48 public shutdownTimestamp;
    /// @notice time in seconds that the manager needs to be restarted after a shutdown, otherwise it must be re-initialized
    uint48 public restartTimeframe;

    /// @notice In situations where a bond market cannot be created, this variable is used to record the OHM capacity for the bond market that needs to be created
    uint256 public bondMarketPendingCapacity;

    // ========== SETUP ========== //

    constructor(
        Kernel kernel_,
        address ohm_,
        address gohm_,
        address reserve_,
        address sReserve_,
        address bondAuctioneer_,
        address cdAuctioneer_,
        address teller_
    ) Policy(kernel_) {
        // Set immutable variables
        if (ohm_ == address(0)) revert InvalidParam("OHM address cannot be 0");
        if (gohm_ == address(0)) revert InvalidParam("gOHM address cannot be 0");
        if (reserve_ == address(0)) revert InvalidParam("DAI address cannot be 0");
        if (sReserve_ == address(0)) revert InvalidParam("sDAI address cannot be 0");
        if (bondAuctioneer_ == address(0))
            revert InvalidParam("Bond Auctioneer address cannot be 0");
        if (teller_ == address(0)) revert InvalidParam("Bond Teller address cannot be 0");
        if (cdAuctioneer_ == address(0)) revert InvalidParam("CD Auctioneer address cannot be 0");

        // Validate that the auctioneer implements the IEnabler interface
        if (!IERC165(cdAuctioneer_).supportsInterface(type(IEnabler).interfaceId))
            revert InvalidParam("CD Auctioneer does not implement IEnabler");

        ohm = ERC20(ohm_);
        gohm = IgOHM(gohm_);
        reserve = ERC20(reserve_);
        sReserve = ERC4626(sReserve_);
        bondAuctioneer = IBondSDA(bondAuctioneer_);
        cdAuctioneer = IConvertibleDepositAuctioneer(cdAuctioneer_);
        teller = teller_;

        _ohmDecimals = ohm.decimals();
        _gohmDecimals = ERC20(gohm_).decimals();
        _reserveDecimals = reserve.decimals();

        // Max approve sReserve contract for reserve for deposits
        reserve.safeApprove(address(sReserve), type(uint256).max);

        // Validate that the CDAuctioneer is configured for the reserve asset
        if (address(cdAuctioneer.getDepositAsset()) != address(reserve_))
            revert InvalidParam("CD Auctioneer not configured for reserve");

        // Emit events
        emit BondContractsSet(bondAuctioneer_, teller_);
        emit ConvertibleDepositAuctioneerSet(cdAuctioneer_);

        // PolicyEnabler disables the policy by default
    }

    /// @inheritdoc Policy
    function configureDependencies() external override returns (Keycode[] memory dependencies) {
        dependencies = new Keycode[](5);
        dependencies[0] = toKeycode("TRSRY");
        dependencies[1] = toKeycode("PRICE");
        dependencies[2] = toKeycode("MINTR");
        dependencies[3] = toKeycode("CHREG");
        dependencies[4] = toKeycode("ROLES");

        TRSRY = TRSRYv1(getModuleAddress(dependencies[0]));
        PRICE = PRICEv1(getModuleAddress(dependencies[1]));
        MINTR = MINTRv1(getModuleAddress(dependencies[2]));
        CHREG = CHREGv1(getModuleAddress(dependencies[3]));
        ROLES = ROLESv1(getModuleAddress(dependencies[4]));

        _oracleDecimals = PRICE.decimals();

        return dependencies;
    }

    /// @inheritdoc Policy
    function requestPermissions()
        external
        view
        override
        returns (Permissions[] memory permissions)
    {
        Keycode mintrKeycode = toKeycode("MINTR");

        permissions = new Permissions[](2);
        permissions[0] = Permissions({
            keycode: mintrKeycode,
            funcSelector: MINTR.increaseMintApproval.selector
        });
        permissions[1] = Permissions({keycode: mintrKeycode, funcSelector: MINTR.mintOhm.selector});

        return permissions;
    }

    function VERSION() external pure returns (uint8 major, uint8 minor) {
        major = 1;
        minor = 2;

        return (major, minor);
    }

    // ========== PERIODIC TASK ========== //

    /// @inheritdoc IPeriodicTask
    /// @dev        This function performs the following:
    ///             - Adjusts the beat counter
    ///             - Exits if the beat counter is not 0
    ///             - Sets the parameters for the auction
    ///             - If the auction tracking period has finished and there is a deficit of OHM sold, attempts to create a bond market
    ///             - If market creation fails (external dependency), emits BondMarketCreationFailed and continues execution
    ///
    ///             Notes:
    ///             - If the CD auction is not running (e.g. the auctioneer contract is disabled), this function will consider OHM to have been under-sold across the auction tracking period. This will result in a bond market being created at the end of the auction tracking period in an attempt to sell the remaining OHM.
    ///             - If there are delays in the heartbeat (which calls this function), auction result tracking will be affected.
    function execute() external onlyRole(ROLE_HEART) {
        // Don't do anything if disabled
        if (!isEnabled) return;

        beatCounter = ++beatCounter % 3;
        if (beatCounter != 0) return;

        if (rateChange.daysLeft != 0) {
            --rateChange.daysLeft;
            if (rateChange.addition) baseEmissionRate += rateChange.changeBy;
            else baseEmissionRate -= rateChange.changeBy;
        }

        // It then calculates the amount to sell for the coming day
        (, , uint256 emission) = getNextEmission();

        // Calculate tick size for the emission
        uint256 calculatedTickSize = getSizeFor(emission);

        // If emission is below the tick size threshold, disable the auction by setting target to 0
        // This prevents auction rounds that would be too small to be economically viable, or would have a price that increases too rapidly
        if (calculatedTickSize == 0) {
            emission = 0;
        }

        // Update the parameters for the convertible deposit auction
        // This call can revert, however it is mitigated by:
        // - CDAuctioneer will accept a target (emission) of 0 (disables auction)
        // - If the tick size is 0, the target will be set to 0 due to the check above
        //
        // Reverts are expected in the following scenarios:
        // - If the target is > 0 (auction active) and minPrice is 0, the call will revert (which we would want)
        cdAuctioneer.setAuctionParameters(
            emission,
            calculatedTickSize,
            getMinPriceFor(_getCurrentPrice())
        );

        // If the tracking period is complete, determine if there was under-selling of OHM
        // This only applies if the auctioneer is enabled. Otherwise, it would create a bond market at every third heartbeat (as the auction results index is not incremented while the auctioneer is disabled)
        if (
            IEnabler(address(cdAuctioneer)).isEnabled() &&
            cdAuctioneer.getAuctionResultsNextIndex() == 0
        ) {
            int256[] memory auctionResults = cdAuctioneer.getAuctionResults();
            int256 difference;
            for (uint256 i = 0; i < auctionResults.length; i++) {
                difference += auctionResults[i];
            }

            // If there was under-selling, create a market to sell the remaining OHM
            if (difference < 0) {
                // Apply the capacity scalar to the remainder
                // Round down in favour of the protocol (fewer emissions)
                /// forge-lint: disable-next-line(unsafe-typecast)
                uint256 scaledCapacity = uint256(-difference).mulDiv(
                    bondMarketCapacityScalar,
                    ONE_HUNDRED_PERCENT
                );

                // Only set pending capacity if the scaled value is non-zero
                if (scaledCapacity > 0) {
                    bondMarketPendingCapacity = scaledCapacity;

                    // Attempt to create the bond market
                    try this.createPendingBondMarket() {
                        // Do nothing if successful
                        // createPendingBondMarket() resets the pending capacity, so it does not need to be done here
                    } catch {
                        // We don't want the periodic task to fail, so catch the error
                        // But trigger an event that can be monitored
                        // Upon failure, the createPendingBondMarket() function can be called again to trigger the bond market
                        emit BondMarketCreationFailed(scaledCapacity);
                    }
                } else {
                    // Ensure that we reset the pending capacity
                    if (bondMarketPendingCapacity > 0) {
                        bondMarketPendingCapacity = 0;
                    }
                }
            }
            // Otherwise, make sure we reset the pending capacity
            // If there is an issue with creating the bond market, this gives the role-holder the auction tracking period to fix the underlying issue and trigger the creation of the market
            else {
                if (bondMarketPendingCapacity > 0) {
                    bondMarketPendingCapacity = 0;
                }
            }
        }
    }

    // ========== INITIALIZE ========== //

    /// @inheritdoc PolicyEnabler
    /// @dev        This function expects the parameters to be an abi-encoded `EnableParams` struct
    function _enable(bytes calldata params_) internal override {
        // Cannot initialize if the restart timeframe hasn't passed since the shutdown timestamp
        // This is specific to re-initializing after a shutdown
        // It will not revert on the first initialization since both values will be zero
        if (shutdownTimestamp + restartTimeframe > uint48(block.timestamp))
            revert CannotRestartYet(shutdownTimestamp + restartTimeframe);

        // Validate that the params are of the correct length
        if (params_.length != ENABLE_PARAMS_LENGTH) revert InvalidParam("params length");

        // Decode the params
        EnableParams memory params = abi.decode(params_, (EnableParams));

        // Validate inputs
        if (params.baseEmissionsRate == 0) revert InvalidParam("baseEmissionRate");
        if (params.minimumPremium == 0) revert InvalidParam("minimumPremium");
        if (params.backing == 0) revert InvalidParam("backing");
        if (params.restartTimeframe == 0) revert InvalidParam("restartTimeframe");
        if (params.tickSize == 0) revert InvalidParam("Tick Size");
        if (params.minPriceScalar < ONE_HUNDRED_PERCENT) revert InvalidParam("Min Price Scalar");
        if (params.bondMarketCapacityScalar > MAX_BOND_MARKET_CAPACITY_SCALAR)
            revert InvalidParam("Bond Market Scalar");

        // Assign
        baseEmissionRate = params.baseEmissionsRate;
        minimumPremium = params.minimumPremium;
        backing = params.backing;
        restartTimeframe = params.restartTimeframe;
        tickSize = params.tickSize;
        minPriceScalar = params.minPriceScalar;
        bondMarketCapacityScalar = params.bondMarketCapacityScalar;

        emit MinimumPremiumChanged(params.minimumPremium);
        emit BackingChanged(params.backing);
        emit RestartTimeframeChanged(params.restartTimeframe);
        emit TickSizeChanged(params.tickSize);
        emit MinPriceScalarChanged(params.minPriceScalar);
        emit BondMarketCapacityScalarChanged(params.bondMarketCapacityScalar);
    }

    // ========== BOND CALLBACK ========== //

    /// @notice callback function for bond market, only callable by the teller
    function callback(uint256 id_, uint256 inputAmount_, uint256 outputAmount_) external {
        // Only callable by the bond teller
        if (msg.sender != teller) revert OnlyTeller();

        // Market ID must match the active market ID stored locally, otherwise revert
        if (id_ != activeMarketId) revert InvalidMarket();

        // Reserve balance should have increased by atleast the input amount
        uint256 reserveBalance = reserve.balanceOf(address(this));
        if (reserveBalance < inputAmount_) revert InvalidCallback();

        // Update backing value with the new reserves added and supply added
        // We do this before depositing the received reserves and minting the output amount of OHM
        // so that the getReserves and getSupply values equal the "previous" values
        // This also conforms to the CEI pattern
        _updateBacking(outputAmount_, inputAmount_);

        // Deposit the reserve balance into the sReserve contract with the TRSRY as the recipient
        // This will sweep any excess reserves into the TRSRY as well
        sReserve.deposit(reserveBalance, address(TRSRY));

        // Mint the output amount of OHM to the Teller
        MINTR.mintOhm(teller, outputAmount_);
    }

    // ========== INTERNAL FUNCTIONS ========== //

    /// @notice create bond protocol market with given budget
    /// @param saleAmount amount of DAI to fund bond market with
    function _createMarket(uint256 saleAmount) internal {
        // Calculate scaleAdjustment for bond market
        // Price decimals are returned from the perspective of the quote token
        // so the operations assume payoutPriceDecimal is zero and quotePriceDecimals
        // is the priceDecimal value
        uint256 minPrice = ((ONE_HUNDRED_PERCENT + minimumPremium) * backing) /
            10 ** _reserveDecimals;
        int8 priceDecimals = _getPriceDecimals(minPrice);
        /// forge-lint: disable-next-line(unsafe-typecast)
        int8 scaleAdjustment = int8(_ohmDecimals) - int8(_reserveDecimals) + (priceDecimals / 2);

        // Calculate oracle scale and bond scale with scale adjustment and format prices for bond market
        /// forge-lint: disable-start(unsafe-typecast)
        uint256 oracleScale = 10 ** uint8(int8(_oracleDecimals) - priceDecimals);
        uint256 bondScale = 10 **
            uint8(
                36 + scaleAdjustment + int8(_reserveDecimals) - int8(_ohmDecimals) - priceDecimals
            );
        /// forge-lint: disable-end(unsafe-typecast)

        // Create new bond market to buy the reserve with OHM
        activeMarketId = bondAuctioneer.createMarket(
            abi.encode(
                IBondSDA.MarketParams({
                    payoutToken: ohm,
                    quoteToken: reserve,
                    callbackAddr: address(this),
                    capacityInQuote: false,
                    capacity: saleAmount,
                    formattedInitialPrice: PRICE.getLastPrice().mulDiv(bondScale, oracleScale),
                    formattedMinimumPrice: minPrice.mulDiv(bondScale, oracleScale),
                    debtBuffer: 100_000, // 100%
                    vesting: vestingPeriod,
                    conclusion: uint48(block.timestamp + 1 days), // 1 day from now
                    depositInterval: uint32(4 hours), // 4 hours
                    scaleAdjustment: scaleAdjustment
                })
            )
        );

        emit SaleCreated(activeMarketId, saleAmount);
    }

    /// @notice allow emission manager to update backing price based on new supply and reserves added
    /// @param supplyAdded number of new OHM minted
    /// @param reservesAdded number of new DAI added
    function _updateBacking(uint256 supplyAdded, uint256 reservesAdded) internal {
        uint256 previousReserves = getReserves();
        uint256 previousSupply = getSupply();

        uint256 percentIncreaseReserves = ((previousReserves + reservesAdded) *
            10 ** _reserveDecimals) / previousReserves;
        uint256 percentIncreaseSupply = ((previousSupply + supplyAdded) * 10 ** _reserveDecimals) /
            previousSupply; // scaled to reserve decimals to match

        backing =
            (backing * percentIncreaseReserves) / // price multiplied by percent increase reserves in reserve scale
            percentIncreaseSupply; // divided by percent increase supply in reserve scale

        // Emit event to track backing changes and results of sales offchain
        emit BackingUpdated(backing, supplyAdded, reservesAdded);
    }

    /// @notice         Helper function to calculate number of price decimals based on the value returned from the price feed.
    /// @param price_   The price to calculate the number of decimals for
    /// @return         The number of decimals
    function _getPriceDecimals(uint256 price_) internal view returns (int8) {
        int8 decimals;
        while (price_ >= 10) {
            price_ = price_ / 10;
            decimals++;
        }

        // Subtract the stated decimals from the calculated decimals to get the relative price decimals.
        // Required to do it this way vs. normalizing at the beginning since price decimals can be negative.
        /// forge-lint: disable-next-line(unsafe-typecast)
        return decimals - int8(_oracleDecimals);
    }

    // ========== ADMIN FUNCTIONS ========== //

    /// @inheritdoc PolicyEnabler
    /// @dev        This function performs the following:
    ///             - Sets the shutdown timestamp
    ///             - Closes the active bond market (if it is active)
    ///             - Disables the convertible deposit auction
    function _disable(bytes calldata) internal override {
        shutdownTimestamp = uint48(block.timestamp);

        // Shutdown the bond market, if it is active
        if (bondAuctioneer.isLive(activeMarketId)) {
            bondAuctioneer.closeMarket(activeMarketId);
        }

        // Disable the convertible deposit auction by setting target to 0
        cdAuctioneer.setAuctionParameters(0, 0, 0);
    }

    /// @notice Restart the emission manager
    function restart() external onlyAdminRole {
        // Restart can be activated only within the specified timeframe since shutdown
        // Outside of this span of time, admin must reinitialize
        if (uint48(block.timestamp) >= shutdownTimestamp + restartTimeframe)
            revert RestartTimeframePassed();

        isEnabled = true;
        emit Enabled();
    }

    /// @notice Rescue any ERC20 token sent to this contract and send it to the TRSRY
    /// @dev This function is restricted to the ADMIN role
    /// @param token_ The address of the ERC20 token to rescue
    function rescue(address token_) external onlyAdminRole {
        ERC20 token = ERC20(token_);
        token.safeTransfer(address(TRSRY), token.balanceOf(address(this)));
    }

    /// @notice Set the base emissions rate
    ///
    /// @param  changeBy_       uint256 added or subtracted from baseEmissionRate
    /// @param  forNumBeats_    uint256 number of times to change baseEmissionRate by changeBy_
    /// @param  add             bool determining addition or subtraction to baseEmissionRate
    function changeBaseRate(
        uint256 changeBy_,
        uint48 forNumBeats_,
        bool add
    ) external onlyAdminRole {
        // Prevent underflow on negative adjustments
        if (!add && (changeBy_ * forNumBeats_ > baseEmissionRate))
            revert InvalidParam("changeBy * forNumBeats");

        // Prevent overflow on positive adjustments
        if (add && (type(uint256).max - changeBy_ * forNumBeats_ < baseEmissionRate))
            revert InvalidParam("changeBy * forNumBeats");

        rateChange = BaseRateChange({changeBy: changeBy_, daysLeft: forNumBeats_, addition: add});

        emit BaseRateChanged(changeBy_, forNumBeats_, add);
    }

    /// @notice Set the minimum premium for emissions
    /// @dev    This function reverts if:
    ///         - newMinimumPremium_ is 0
    ///
    /// @param  newMinimumPremium_  The new minimum premium, in terms of ONE_HUNDRED_PERCENT
    function setMinimumPremium(uint256 newMinimumPremium_) external onlyAdminRole {
        if (newMinimumPremium_ == 0) revert InvalidParam("newMinimumPremium");

        minimumPremium = newMinimumPremium_;

        emit MinimumPremiumChanged(newMinimumPremium_);
    }

    /// @notice Set the new bond vesting period in seconds
    /// @dev    This function reverts if:
    ///         - newVestingPeriod_ is more than 31536000 (1 year in seconds)
    ///
    /// @param newVestingPeriod_ uint48
    function setVestingPeriod(uint48 newVestingPeriod_) external onlyAdminRole {
        // Verify that the vesting period isn't more than a year
        // This check helps ensure a timestamp isn't input instead of a duration
        if (newVestingPeriod_ > uint48(31536000)) revert InvalidParam("newVestingPeriod");
        vestingPeriod = newVestingPeriod_;

        emit VestingPeriodChanged(newVestingPeriod_);
    }

    /// @notice Allow governance to adjust backing price if deviated from reality
    /// @dev    This function reverts if:
    ///         - newBacking is 0
    ///         - newBacking is less than 90% of current backing (to prevent large sudden drops)
    ///
    ///         Note: if adjustment is more than 33% down, contract should be redeployed
    ///
    /// @param  newBacking  to adjust to
    /// TODO maybe put in a timespan arg so it can be smoothed over time if desirable
    function setBacking(uint256 newBacking) external onlyAdminRole {
        // Backing cannot be reduced by more than 10% at a time
        if (newBacking == 0 || newBacking < (backing * 9) / 10) revert InvalidParam("newBacking");
        backing = newBacking;

        emit BackingChanged(newBacking);
    }

    /// @notice Allow governance to adjust the timeframe for restart after shutdown
    /// @dev    This function reverts if:
    ///         - newTimeframe is 0
    ///
    /// @param  newTimeframe    to adjust it to
    function setRestartTimeframe(uint48 newTimeframe) external onlyAdminRole {
        // Restart timeframe must be greater than 0
        if (newTimeframe == 0) revert InvalidParam("newRestartTimeframe");

        restartTimeframe = newTimeframe;

        emit RestartTimeframeChanged(newTimeframe);
    }

    /// @notice allow governance to set the bond contracts used by the emission manager
    /// @dev    This function reverts if:
    ///         - bondAuctioneer_ is the zero address
    ///         - teller_ is the zero address
    ///
    /// @param  bondAuctioneer_ address of the bond auctioneer contract
    /// @param  teller_         address of the bond teller contract
    function setBondContracts(address bondAuctioneer_, address teller_) external onlyAdminRole {
        // Bond contracts cannot be set to the zero address
        if (bondAuctioneer_ == address(0)) revert InvalidParam("bondAuctioneer");
        if (teller_ == address(0)) revert InvalidParam("teller");

        bondAuctioneer = IBondSDA(bondAuctioneer_);
        teller = teller_;

        emit BondContractsSet(bondAuctioneer_, teller_);
    }

    /// @notice Allow governance to set the CD contract used by the emission manager
    /// @dev    This function reverts if:
    ///         - cdAuctioneer_ is the zero address
    ///         - The deposit asset of the CDAuctioneer is not the same as the reserve asset in this contract
    ///
    /// @param  cdAuctioneer_   address of the cd auctioneer contract
    function setCDAuctionContract(address cdAuctioneer_) external onlyAdminRole {
        // Auction contract cannot be set to the zero address
        if (cdAuctioneer_ == address(0)) revert InvalidParam("zero address");
        // Validate that the CDAuctioneer is configured for the reserve asset
        if (
            address(IConvertibleDepositAuctioneer(cdAuctioneer_).getDepositAsset()) !=
            address(reserve)
        ) revert InvalidParam("different asset");

        cdAuctioneer = IConvertibleDepositAuctioneer(cdAuctioneer_);

        emit ConvertibleDepositAuctioneerSet(cdAuctioneer_);
    }

    /// @notice Allow governance to set the CD tick size
    /// @dev    This function reverts if:
    ///         - newTickSize_ is 0
    ///
    /// @param  newTickSize_    as a fixed amount in OHM decimals (9)
    function setTickSize(uint256 newTickSize_) external onlyAdminRole {
        if (newTickSize_ == 0) revert InvalidParam("Tick Size");
        tickSize = newTickSize_;

        emit TickSizeChanged(newTickSize_);
    }

    /// @notice Allow governance to set the CD minimum price scalar
    /// @dev    This function reverts if:
    ///         - newScalar is less than ONE_HUNDRED_PERCENT (100% in 18 decimals)
    ///
    /// @param  newScalar   as a percentage in 18 decimals
    function setMinPriceScalar(uint256 newScalar) external onlyAdminRole {
        if (newScalar < ONE_HUNDRED_PERCENT) revert InvalidParam("Min Price Scalar");
        minPriceScalar = newScalar;

        emit MinPriceScalarChanged(newScalar);
    }

    /// @notice Allow governance to set the bond market capacity scalar
    /// @dev    This function reverts if:
    ///         - newScalar is greater than MAX_BOND_MARKET_CAPACITY_SCALAR (200%)
    ///
    /// @param  newScalar   as a percentage in 18 decimals
    function setBondMarketCapacityScalar(uint256 newScalar) external onlyAdminRole {
        if (newScalar > MAX_BOND_MARKET_CAPACITY_SCALAR)
            revert InvalidParam("Bond Market Capacity Scalar");

        bondMarketCapacityScalar = newScalar;
        emit BondMarketCapacityScalarChanged(newScalar);
    }

    // =========- VIEW FUNCTIONS ========== //

    /// @notice return reserves, measured as clearinghouse receivables and sReserve balances, in reserve denomination
    function getReserves() public view returns (uint256 reserves) {
        uint256 chCount = CHREG.registryCount();
        for (uint256 i; i < chCount; i++) {
            reserves += IGenericClearinghouse(CHREG.registry(i)).principalReceivables();
            uint256 bal = sReserve.balanceOf(CHREG.registry(i));
            if (bal > 0) reserves += sReserve.previewRedeem(bal);
        }

        reserves += sReserve.previewRedeem(sReserve.balanceOf(address(TRSRY)));
    }

    /// @notice return supply, measured as supply of gOHM in OHM denomination
    function getSupply() public view returns (uint256 supply) {
        return (gohm.totalSupply() * gohm.index()) / 10 ** _gohmDecimals;
    }

    /// @notice return the current premium as a percentage where 1e18 is 100%
    function getPremium() public view returns (uint256) {
        uint256 price = PRICE.getLastPrice();
        uint256 pbr = (price * 10 ** _reserveDecimals) / backing;
        return pbr > ONE_HUNDRED_PERCENT ? pbr - ONE_HUNDRED_PERCENT : 0;
    }

    /// @notice return the next sale amount, premium, emission rate, and emissions based on the current premium
    function getNextEmission()
        public
        view
        returns (uint256 premium, uint256 emissionRate, uint256 emission)
    {
        // To calculate the sale, it first computes premium (market price / backing price) - 100%
        premium = getPremium();

        // If the premium is greater than the minimum premium, it computes the emission rate and nominal emissions
        if (premium >= minimumPremium) {
            emissionRate =
                (baseEmissionRate * (ONE_HUNDRED_PERCENT + premium)) /
                (ONE_HUNDRED_PERCENT + minimumPremium); // in OHM scale
            emission = (getSupply() * emissionRate) / 10 ** _ohmDecimals; // OHM Scale * OHM Scale / OHM Scale = OHM Scale
        }
    }

    /// @notice Get the auction tick size for a given target
    /// @dev    Returns the standard tick size if the target emission is at least the standard tick size.
    ///         Otherwise, 0 is returned to indicate that the auction should be disabled.
    ///
    /// @param  target size of day's CD auction
    /// @return size of tick
    function getSizeFor(uint256 target) public view returns (uint256 size) {
        if (target < tickSize) return 0;

        return tickSize;
    }

    /// @notice Get CD auction minimum price for a given price input
    /// @dev    Expects `price` to already be expressed in the reserve asset's decimal scale.
    ///         This function does not adjust/convert decimal scales.
    ///
    /// @param  price Price of OHM in reserve token terms, scaled to the reserve asset's decimals
    function getMinPriceFor(uint256 price) public view returns (uint256) {
        // Round in favour of the protocol
        return price.mulDivUp(minPriceScalar, ONE_HUNDRED_PERCENT);
    }

    /// @notice Returns the current price from the PRICE module
    ///
    /// @return currentPrice with decimal scale of the reserve asset
    function _getCurrentPrice() internal view returns (uint256) {
        // Get from PRICE
        uint256 currentPrice = PRICE.getCurrentPrice();

        // Change the decimal scale to be the reserve asset's
        return currentPrice.mulDiv(10 ** _reserveDecimals, 10 ** _oracleDecimals);
    }

    // ========== BOND MARKET CREATION ========== //

    /// @notice Creates a bond market
    /// @dev    Notes:
    ///         - If there is no pending capacity, no bond market will be created
    ///
    ///         This function will revert if:
    ///         - The caller is not this contract, or an address with the admin/manager role
    ///         - The contract is disabled
    ///         - The bond market cannot be created
    function createPendingBondMarket() external onlyEnabled {
        // Validate that the caller is this contract or an admin/manager
        if (msg.sender != address(this) && !_isManager(msg.sender) && !_isAdmin(msg.sender))
            revert NotAuthorised();

        // If there is no pending capacity, skip
        if (bondMarketPendingCapacity == 0) return;

        // Create the market
        MINTR.increaseMintApproval(address(this), bondMarketPendingCapacity);
        _createMarket(bondMarketPendingCapacity);

        // Set the pending capacity to 0
        // This prevents the bond market from being created again
        bondMarketPendingCapacity = 0;
    }

    // ========== ERC165 ========== //

    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override(PolicyEnabler, IPeriodicTask) returns (bool) {
        return
            interfaceId == bytes4(0x01ffc9a7) || // ERC-165
            interfaceId == type(IPeriodicTask).interfaceId ||
            interfaceId == type(IEmissionManager).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}
/// forge-lint: disable-end(mixed-case-function, mixed-case-variable, screaming-snake-case-immutable)
"
    },
    "dependencies/solmate-6.2.0/src/tokens/ERC20.sol": {
      "content": "// 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);
    }
}
"
    },
    "dependencies/solmate-6.2.0/src/mixins/ERC4626.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";

/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
    using SafeTransferLib for ERC20;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /*//////////////////////////////////////////////////////////////
                               IMMUTABLES
    //////////////////////////////////////////////////////////////*/

    ERC20 public immutable asset;

    constructor(
        ERC20 _asset,
        string memory _name,
        string memory _symbol
    ) ERC20(_name, _symbol, _asset.decimals()) {
        asset = _asset;
    }

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    //////////////////////////////////////////////////////////////*/

    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.
        asset.safeTransferFrom(msg.sender, address(this), assets);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        afterDeposit(assets, shares);
    }

    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.
        asset.safeTransferFrom(msg.sender, address(this), assets);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        afterDeposit(assets, shares);
    }

    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);

        asset.safeTransfer(receiver, assets);
    }

    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);

        asset.safeTransfer(receiver, assets);
    }

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    //////////////////////////////////////////////////////////////*/

    function totalAssets() public view virtual returns (uint256);

    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.mulDivDown(supply, totalAssets());
    }

    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.mulDivDown(totalAssets(), supply);
    }

    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    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);
    }

    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());
    }

    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return convertToAssets(balanceOf[owner]);
    }

    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 {}
}
"
    },
    "src/libraries/TransferHelper.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "solmate/tokens/ERC20.sol";

/// @notice Safe ERC20 and ETH transfer library that safely handles missing return values.
/// @author Modified from Uniswap & old Solmate (https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/libraries/TransferHelper.sol)
library TransferHelper {
    function safeTransferFrom(ERC20 token, address from, address to, uint256 amount) internal {
        (bool success, bytes memory data) = address(token).call(
            abi.encodeWithSelector(ERC20.transferFrom.selector, from, to, amount)
        );

        require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(ERC20 token, address to, uint256 amount) internal {
        (bool success, bytes memory data) = address(token).call(
            abi.encodeWithSelector(ERC20.transfer.selector, to, amount)
        );

        require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAILED");
    }

    function safeApprove(ERC20 token, address to, uint256 amount) internal {
        (bool success, bytes memory data) = address(token).call(
            abi.encodeWithSelector(ERC20.approve.selector, to, amount)
        );

        require(success && (data.length == 0 || abi.decode(data, (bool))), "APPROVE_FAILED");
    }
}
"
    },
    "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
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                require(denominator > 0);
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (type(uint256).max - denominator + 1) & denominator;
            // Divide denominator by power of two
            assembly {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly {
                prod0 := div(prod0, twos)
            }
            // Shift in bits from prod1 into prod0. For this we need
            // to flip `twos` such that it is 2**256 / twos.
            // If twos is zero, then it becomes one
            assembly {
                twos := add(div(sub(0, twos), twos), 1)
            }
            prod0 |= prod1 * twos;

            // Invert denominator mod 2**256
            // Now that denominator is an odd number, it has an inverse
            // modulo 2**256 such that denominator * inv = 1 mod 2**256.
            // Compute the inverse by starting with a seed that is correct
            // correct for four bits. That is, denominator * inv = 1 mod 2**4
            uint256 inv = (3 * denominator) ^ 2;
            // 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 *= 2 - denominator * inv; // inverse mod 2**8
            inv *= 2 - denominator * inv; // inverse mod 2**16
            inv *= 2 - denominator * inv; // inverse mod 2**32
            inv *= 2 - denominator * inv; // inverse mod 2**64
            inv *= 2 - denominator * inv; // inverse mod 2**128
            inv *= 2 - denominator * inv; // inverse mod 2**256

            // Because the division is now exact we can divide by multiplying
            // with the modular inverse of denominator. This will give us the
            // correct result modulo 2**256. Since the precoditions guarantee
            // that the outcome is less than 2**256, this is the final result.
            // We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inv;
            return result;
        }
    }

    /// @notice Calculates ceil(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
    function mulDivUp(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        result = mulDiv(a, b, denominator);
        unchecked {
            if (mulmod(a, b, denominator) > 0) {
                require(result < type(uint256).max);
                result++;
            }
        }
    }
}
"
    },
    "src/interfaces/IBondSDA.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;

import {ERC20} from "solmate/tokens/ERC20.sol";
import {IBondAuctioneer} from "../interfaces/IBondAuctioneer.sol";

interface IBondSDA is IBondAuctioneer {
    /// @notice Main information pertaining to bond market
    struct BondMarket {
        address owner; // market owner. sends payout tokens, receives quote tokens (defaults to creator)
        ERC20 payoutToken; // token to pay depositors with
        ERC20 quoteToken; // token to accept as payment
        address callbackAddr; // address to call for any operations on bond purchase. Must inherit to IBondCallback.
        bool capacityInQuote; // capacity limit is in payment token (true) or in payout (false, default)
        uint256 capacity; // capacity remaining
        uint256 totalDebt; // total payout token debt from market
        uint256 minPrice; // minimum price (hard floor for the market)
        uint256 maxPayout; // max payout tokens out in one order
        uint256 sold; // payout tokens out
        uint256 purchased; // quote tokens in
        uint256 scale; // scaling factor for the market (see MarketParams struct)
    }

    /// @notice Information used to control how a bond market changes
    struct BondTerms {
        uint256 controlVariable; // scaling variable for price
        uint256 maxDebt; // max payout token debt accrued
        uint48 vesting; // length of time from deposit to expiry if fixed-term, vesting timestamp if fixed-expiry
        uint48 conclusion; // timestamp when market no longer offered
    }

    /// @notice Data needed for tuning bond market
    /// @dev Durations are stored in uint32 (not int32) and timestamps are stored in uint48, so is not subject to Y2K38 overflow
    struct BondMetadata {
        uint48 lastTune; // last timestamp when control variable was tuned
        uint48 lastDecay; // last timestamp when market was created and debt was decayed
        uint32 length; // time from creation to conclusion.
        uint32 depositInterval; // target frequency of deposits
        uint32 tuneInterval; // frequency of tuning
        uint32 tuneAdjustmentDelay; // time to implement downward tuning adjustments
        uint32 debtDecayInterval; // interval over which debt should decay completely
        uint256 tuneIntervalCapacity; // capacity expected to be used during a tuning interval
        uint256 tuneBelowCapacity; // capacity that the next tuning will occur at
        uint256 lastTuneDebt; // target debt calculated at last tuning
    }

    /// @notice Control variable adjustment data
    struct Adjustment {
        uint256 change;
        uint48 lastAdjustment;
        uint48 timeToAdjusted; // how long until adjustment happens
        bool active;
    }

    /// @notice             Parameters to create a new bond market
    /// @dev                Note price should be passed in a specific format:
    ///                     formatted price = (payoutPriceCoefficient / quotePriceCoefficient)
    ///                             * 10**(36 + scaleAdjustment + quoteDecimals - payoutDecimals + payoutPriceDecimals - quotePriceDecimals)
    ///                     where:
    ///                         payoutDecimals - Number of decimals defined for the payoutToken in its ERC20 contract
    ///                         quoteDecimals - Number of decimals defined for the quoteToken in its ERC20 contract
    ///                         payoutPriceCoefficient - The coefficient of the payoutToken price in scientific notation (also known as the significant digits)
    ///                         payoutPriceDecimals - The significand of the payoutToken price in scientific notation (also known as the base ten exponent)
    ///                         quotePriceCoefficient - The coefficient of the quoteToken price in scientific notation (also known as the significant digits)
    ///                         quotePriceDecimals - The significand of the quoteToken price in scientific notation (also known as the base ten exponent)
    ///                         scaleAdjustment - see below
    ///                         * In the above definitions, the "prices" need to have the same unit of account (i.e. both in OHM, $, ETH, etc.)
    ///                         If price is not provided in this format, the market will not behave as intended.
    /// @param params_      Encoded bytes array, with the following elements
    /// @dev                    0. Payout Token (token paid out)
    /// @dev                    1. Quote Token (token to be received)
    /// @dev                    2. Callback contract address, should conform to IBondCallback. If 0x00, tokens will be transferred from market.owner
    /// @dev                    3. Is Capacity in Quote Token?
    /// @dev                    4. Capacity (amount in quoteDecimals or amount in payoutDecimals)
    /// @dev                    5. Formatted initial price (see note above)
    /// @dev                    6. Formatted minimum price (see note above)
    /// @dev                    7. Debt buffer. Percent with 3 decimals. Percentage over the initial debt to allow the market to accumulate at anyone time.
    /// @dev                       Works as a circuit breaker for the market in case external conditions incentivize massive buying (e.g. stablecoin depeg).
    /// @dev                       Minimum is the greater of 10% or initial max payout as a percentage of capacity.
    /// @dev                       If the value is too small, the market will not be able function normally and close prematurely.
    /// @dev                       If the value is too large, the market will not circuit break when intended. The value must be > 10% but can exceed 100% if desired.
    /// @dev                       A good heuristic to calculate a debtBuffer with is to determine the amount of capacity that you think is reasonable to be expended
    /// @dev                       in a short duration as a percent, e.g. 25%. Then a reasonable debtBuffer would be: 0.25 * 1e3 * decayInterval / marketDuration
    /// @dev                       where decayInterval = max(3 days, 5 * depositInterval) and marketDuration = conclusion - creation time.
    /// @dev                    8. Is fixed term ? Vesting length (seconds) : Vesting expiry (timestamp).
    /// @dev                        A 'vesting' param longer than 50 years is considered a timestamp for fixed expiry.
    /// @dev                    9. Conclusion (timestamp)
    /// @dev                    10. Deposit interval (seconds)
    /// @dev                    11. Market scaling factor adjustment, ranges from -24 to +24 within the configured market bounds.
    /// @dev                        Should be calculated as: (payoutDecimals - quoteDecimals) - ((payoutPriceDecimals - quotePriceDecimals) / 2)
    /// @dev                        Providing a scaling factor adjustment that doesn't follow this formula could lead to under or overflow errors in the market.
    /// @return                 ID of new bond market
    struct MarketParams {
        ERC20 payoutToken;
        ERC20 quoteToken;
        address callbackAddr;
        bool capacityInQuote;
        uint256 capacity;
        uint256 formattedInitialPrice;
        uint256 formattedMinimumPrice;
        uint32 debtBuffer;
        uint48 vesting;
        uint48 conclusion;
        uint32 depositInterval;
        int8 scaleAdjustment;
    }

    /* ========== VIEW FUNCTIONS ========== */

    /// @notice             Calculate current market price of payout token in quote tokens
    /// @dev                Accounts for debt and control variable decay since last deposit (vs _marketPrice())
    /// @param id_          ID of market
    /// @return             Price for market in configured decimals (see Ma

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Swap, Yield, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xb4f620c39f3ba4a1e7ad264fed6239b0c618db50|verified:true|block:23748075|tx:0x078dd3b6542d2d8e9c7ca689aa920d464a10e409336d9fd076ada0e5eb09aca9|first_check:1762530492

Submitted on: 2025-11-07 16:48:13

Comments

Log in to comment.

No comments yet.