-
Notifications
You must be signed in to change notification settings - Fork 26
Exp: DiscreteCurveMathLib_v1 & FM_BC_Discrete_Redeeming_VirtualSupply_v1
#754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: house-protocol
Are you sure you want to change the base?
Conversation
leeftk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an initial review, I'll still be looking at this until Wednesday, so will add more but this is enough to get us started.
src/modules/fundingManager/bondingCurve/FM_BC_Discrete_Redeeming_VirtualSupply_v1.sol
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/FM_BC_Discrete_Redeeming_VirtualSupply_v1.sol
Show resolved
Hide resolved
| fullStepBacking += stepCollateralCapacity_; | ||
| } else { | ||
| // Partial step purchase and exit | ||
| uint partialIssuance_ = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's redundant calculation happening here. Lines 427-432 are calculating the same thing twice:
- First we calculate
partialIssuance_fromremainingBudget_usingMath.mulDiv - Then we recalculate the collateral cost from
partialIssuance_usingFixedPointMathLib._mulDivUp
This is redundant because we're essentially doing: remainingBudget_ * SCALING_FACTOR / stepPrice_ then result * stepPrice_ / SCALING_FACTOR, which should give us back remainingBudget_ (with potential rounding differences).
The issue is that Math.mulDiv rounds down while _mulDivUp rounds up, so these calculations might not match exactly. We should just track the actual budget consumption instead of recalculating it.
Something like:
uint partialIssuance_ = Math.mulDiv(remainingBudget_, SCALING_FACTOR, stepPrice_);
tokensToMint_ += partialIssuance_;
// Use actual budget consumed instead of recalculating
collateralSpentByPurchaser_ = collateralToSpendProvided_ - remainingBudget_;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are exactly right. Already refactored this to eliminate the redundant calculations. The new version calculates partialIssuance_ once using Math.mulDiv(remainingBudget_, SCALING_FACTOR, stepPrice_) and then tracks budget consumption with collateralSpentByPurchaser_ = collateralToSpendProvided_ - remainingBudget_ at the end. This should avoid the rounding inconsistency between Math.mulDiv (rounds down) and FixedPointMathLib._mulDivUp (rounds up) that was causing the A ÷ B × B ≠ A issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing we can set this as resolved then ser!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Outdated
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Outdated
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Outdated
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/FM_BC_Discrete_Redeeming_VirtualSupply_v1.sol
Outdated
Show resolved
Hide resolved
src/modules/fundingManager/bondingCurve/formulas/DiscreteCurveMathLib_v1.sol
Show resolved
Hide resolved
…/contracts into experimental/per
This reverts commit ab7ef63.
DiscreteCurveMathLib_v1
Purpose of Contract
The
DiscreteCurveMathLib_v1is a Solidity library designed to provide mathematical operations for discrete bonding curves. Its primary purpose is to perform calculations related to price, collateral reserve, purchase returns (issuance out for collateral in), and sale returns (collateral out for issuance in) in a gas-efficient manner. This efficiency is achieved mainly through the use of packed storage for curve segment data. It is intended to be used by other smart contracts, such as Funding Managers, that implement bonding curve logic.Glossary
To understand the functionalities of this library and its context, it is important to be familiar with the following definitions.
supplyPerStep) can be bought or sold at a fixed price.type PackedSegment is bytes32;) used by this library to store all four parameters of a curve segment into a singlebytes32value. This optimizes storage gas costs.PackedSegmentLib../libraries/PackedSegmentLib.sol), imported byDiscreteCurveMathLib_v1, responsible for the creation, validation, packing, and unpacking ofPackedSegmentdata.1e18)10^18) used for fixed-point arithmetic to handle decimal precision for prices and token amounts, assuming standard 18-decimal tokens.MAX_SEGMENTS10) defining the maximum number of segments a curve configuration can have, enforced by functions like_validateSegmentArrayand_calculateReserveForSupply.Implementation Design Decision
The purpose of this section is to inform the user about important design decisions made during the development process. The focus should be on why a certain decision was made and how it has been implemented.
Type-Safe Packed Storage for Segments
The core design decision for
DiscreteCurveMathLib_v1is the use of type-safe packed storage for bonding curve segment data. Each segment's configuration (initial price, price increase per step, supply per step, and number of steps) is packed into a singlebytes32slot using the custom typePackedSegmentand the internal helper libraryPackedSegmentLib.bytes32value, each segment effectively consumes only one storage slot when stored by a calling contract (e.g., in an arrayPackedSegment[] storage segments;). This significantly reduces gas costs for deployment and state modification of contracts that manage multiple curve segments.PackedSegmentLibdefines the bit allocation for each parameter within thebytes32value, along with masks and offsets. It provides:createfunction that validates input parameters against their bit limits and packs them.initialPrice,priceIncrease, etc.) to retrieve individual parameters.unpackfunction to retrieve all parameters at once.The
PackedSegmenttype itself (beingbytes32) ensures type safety, preventing accidental mixing with otherbytes32values that do not represent curve segments.Segment Validation Rules
To ensure economic sensibility and robustness,
DiscreteCurveMathLib_v1and its helperPackedSegmentLibenforce specific validation rules for segment configurations:No Free Segments (
PackedSegmentLib._create):Segments that are entirely "free" – meaning their
initialPriceis 0 AND theirpriceIncreasePerStepis also 0 – are disallowed. Attempting to create such a segment will causePackedSegmentLib._create()to revert with the errorIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__SegmentIsFree(). This prevents scenarios where tokens could be minted indefinitely at no cost from a segment that never increases in price.Non-Decreasing Price Progression (
DiscreteCurveMathLib_v1._validateSegmentArray):When an array of segments is validated using
DiscreteCurveMathLib_v1._validateSegmentArray(), the library checks for logical price progression between consecutive segments. Specifically, theinitialPriceof any segmentN+1must be greater than or equal to the calculated final price of the preceding segmentN. The final price of segmentNis determined assegments[N]._initialPrice() + (segments[N]._numberOfSteps() - 1) * segments[N]._priceIncrease().If this condition is violated (i.e., if a subsequent segment starts at a lower price than where the previous one ended),
_validateSegmentArray()will revert with the errorIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__InvalidPriceProgression(uint256 segmentIndex, uint256 previousSegmentFinalPrice, uint256 nextSegmentInitialPrice).This rule ensures a generally non-decreasing (or strictly increasing, if price increases are positive) price curve across the entire set of segments.
Specific Segment Structure ("True Flat" / "True Sloped") (
PackedSegmentLib._create):PackedSegmentLib._create()enforces specific structural rules for segments:numberOfSteps == 1andpriceIncreasePerStep == 0. Attempting to create a multi-step flat segment (e.g.,numberOfSteps > 1andpriceIncreasePerStep == 0) will revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__InvalidFlatSegment().numberOfSteps > 1andpriceIncreasePerStep > 0. Attempting to create a single-step sloped segment (e.g.,numberOfSteps == 1andpriceIncreasePerStep > 0) will revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__InvalidPointSegment().These rules ensure that segments are clearly defined as either single-step fixed-price points or multi-step incrementally priced slopes. The
priceIncreasePerStepbeinguint256inherently prevents price decreases within a single segment.Other Important Validation Rules & Errors:
_validateSegmentArray,_calculateReserveForSupply,_calculatePurchaseReturnvia internal checks): If an operation requiring segments is attempted but no segments are defined (e.g.,segments_array is empty), the library may revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__NoSegmentsConfigured()._validateSegmentArray,_calculateReserveForSupply): If the providedsegments_array exceedsMAX_SEGMENTS(currently 10), relevant functions will revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__TooManySegments()._validateSupplyAgainstSegments,_findPositionForSupply): If a target supply or current supply exceeds the total possible supply defined by all segments, functions will revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__SupplyExceedsCurveCapacity(uint256 currentSupplyOrTarget, uint256 totalCapacity)._calculatePurchaseReturn): Attempting a purchase with zero collateral reverts withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__ZeroCollateralInput()._calculateSaleReturn): Attempting a sale with zero issuance tokens reverts withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__ZeroIssuanceInput()._calculateSaleReturn): Attempting to sell more tokens than thecurrentTotalIssuanceSupplyreverts withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__InsufficientIssuanceToSell(uint256 tokensToSell, uint256 currentSupply).Note: All custom errors mentioned (e.g.,
DiscreteCurveMathLib__SegmentIsFree,DiscreteCurveMathLib__InvalidPriceProgression,DiscreteCurveMathLib__InvalidFlatSegment,DiscreteCurveMathLib__InvalidPointSegment,DiscreteCurveMathLib__NoSegmentsConfigured,DiscreteCurveMathLib__TooManySegments,DiscreteCurveMathLib__SupplyExceedsCurveCapacity,DiscreteCurveMathLib__ZeroCollateralInput,DiscreteCurveMathLib__ZeroIssuanceInput,DiscreteCurveMathLib__InsufficientIssuanceToSell) must be defined in theIDiscreteCurveMathLib_v1.solinterface file for the contracts to compile and function correctly.Efficient Calculation Methods
To further optimize gas for on-chain computations:
priceIncreasePerStep > 0), functions like_calculateReserveForSupply(and its internal helper_calculateSegmentReserve) use the mathematical formula for the sum of an arithmetic series. This allows calculating the total collateral for multiple steps without iterating through each step individually, saving gas._calculatePurchaseReturnfunction uses a direct iterative approach to determine the number of tokens to be minted for a given collateral input. It iterates through the curve segments and steps, calculating the cost for each, until the provided collateral is exhausted or the curve capacity is reached._calculateSaleReturnfunction determines the collateral out by calculating the total reserve locked in the curve before and after the sale, then taking the difference. This approach (R(S_current) - R(S_final)) is generally more efficient than iterating backward through curve steps. It leverages the internal helper_calculateReservesForTwoSuppliesto efficiently get both reserve values in a single pass.Internal Helper Functions for Calculation:
The library utilizes several internal helper functions to achieve its calculations efficiently and maintain modularity:
_validateSupplyAgainstSegments: Ensures a given supply is consistent with the curve's capacity._calculateSegmentReserve: Calculates the reserve for a portion of a single segment, handling flat and sloped logic._calculateReservesForTwoSupplies: An optimized helper for_calculateSaleReturnthat calculates reserves for two different supply points in one pass.While these are internal, understanding their role can be helpful for a deeper analysis of the library's mechanics.
Limitations of Packed Storage and Low-Priced Collateral
While
PackedSegmentoffers significant gas savings, its fixed bit allocation for price and supply parameters introduces limitations, particularly when dealing with collateral tokens that have a very low price per unit but maintain a high decimal precision (e.g., 18 decimals).The Core Issue:
The
initialPriceandpriceIncreasefields currently use 72 bits each.2^72 - 1(approx.4.722 x 10^21) wei.4,722,366.48tokens.Impact of Low-Priced Collateral:
If a collateral token is worth, for example, $0.000001 (one micro-dollar) and has 18 decimals:
initialPriceorpriceIncreaseis4,722,366.48 tokens * $0.000001/token = ~$4.72.This means a bonding curve segment could not have an initial step price or a price increment (if denominated in such a collateral token) that represents more than ~$4.72 worth of that token.
Example: Extremely Low-Priced Token
Consider a hypothetical 18-decimal token worth $0.0000000001 (one-tenth of a nano-dollar).
1 / $0.0000000001 = 10,000,000,000tokens.10,000,000,000 * 1e18 = 1e28wei.This value (
1e28wei) significantly exceeds the2^72 - 1(approx.4.7e21) wei capacity of the 72-bit price fields, leading to an overflow if one tried to set a price step equivalent to $1.00 of this token.Potential Solutions and Workarounds:
Collateral Token Choice & Decimal Precision:
Price Scaling Factor:
PRICE_SCALING_FACTORof1e12could be used. A packed price of1would then represent an actual price of1 * 1e12. This allows storing scaled-down values inPackedSegmentwhile representing larger actual prices.Alternative Bit Allocation in
PackedSegment:bytes32slot per segment if necessary. For instance, allocating 96 bits for price would allow values up to2^96 - 1(approx.7.9e28wei), accommodating even extremely low-priced 18-decimal tokens.Protocol-Level Policies:
Assessment for Current
DiscreteCurveMathLib_v1:The current 72-bit allocation for prices is a deliberate trade-off favoring gas efficiency and is generally sufficient for many common use cases, especially with typical collateral like ETH, wBTC, or stablecoins (USDC, USDT, DAI) which have prices or decimal counts that fit well. For protocols like House Protocol, which are likely to use established stablecoins, the existing 72-bit precision for prices should provide ample headroom.
The library is well-suited for its primary intended applications. If support for extremely micro-cap tokens with high decimal precision becomes a strict requirement, deploying a new version of the library with adjusted bit allocations or incorporating an explicit price scaling mechanism in the consuming contract would be the recommended approaches.
Internal Functions and Composability
Most functions in the library are
internal pure, designed to be called by other smart contracts (typically Funding Managers). This makes the library a set of reusable mathematical tools rather than a standalone stateful contract. Theusing PackedSegmentLib for PackedSegment;directive enables convenient syntax for accessing segment data (e.g.,mySegment._initialPrice()).Inheritance
UML Class Diagram
This diagram illustrates the relationships between the library, its internal helper library, and associated types/interfaces.
classDiagram direction LR note "DiscreteCurveMathLib_v1 is a Solidity library providing pure functions for bonding curve calculations." class DiscreteCurveMathLib_v1 { <<library>> +SCALING_FACTOR : uint256 +MAX_SEGMENTS : uint256 --- #_findPositionForSupply(PackedSegment[] memory, uint256) internal pure returns (uint segmentIndex, uint stepIndexWithinSegment, uint priceAtCurrentStep) #_calculateReserveForSupply(PackedSegment[] memory, uint256) internal pure returns (uint256 totalReserve_) #_calculatePurchaseReturn(PackedSegment[] memory, uint256, uint256) internal pure returns (uint256 tokensToMint_, uint256 collateralSpentByPurchaser_) #_calculateSaleReturn(PackedSegment[] memory, uint256, uint256) internal pure returns (uint256 collateralToReturn_, uint256 tokensToBurn_) #_createSegment(uint256, uint256, uint256, uint256) internal pure returns (PackedSegment) #_validateSegmentArray(PackedSegment[] memory) internal pure #_validateSupplyAgainstSegments(PackedSegment[] memory, uint256) internal pure returns (uint totalCurveCapacity_) #_calculateReservesForTwoSupplies(PackedSegment[] memory, uint256, uint256) internal pure returns (uint lowerReserve_, uint higherReserve_) #_calculateSegmentReserve(uint256, uint256, uint256, uint256) internal pure returns (uint collateral_) } class PackedSegmentLib { <<library>> -INITIAL_PRICE_BITS : uint256 -PRICE_INCREASE_BITS : uint256 -SUPPLY_BITS : uint256 -STEPS_BITS : uint256 --- #_create(uint256, uint256, uint256, uint256) internal pure returns (PackedSegment) #_initialPrice(PackedSegment) internal pure returns (uint256) #_priceIncrease(PackedSegment) internal pure returns (uint256) #_supplyPerStep(PackedSegment) internal pure returns (uint256) #_numberOfSteps(PackedSegment) internal pure returns (uint256) #_unpack(PackedSegment) internal pure returns (uint256, uint256, uint256, uint256) } class PackedSegment { <<type is bytes32>> note "User-defined value type wrapping bytes32" } class IDiscreteCurveMathLib_v1 { <<interface>> +Errors... +Events... // Note: CurvePosition struct might be defined here if used by _findPositionForSupply's NatSpec, // but the function itself returns a tuple. // SegmentConfig struct is not directly used by _createSegment's signature. } note for DiscreteCurveMathLib_v1 "Uses PackedSegmentLib for segment data manipulation" DiscreteCurveMathLib_v1 ..> PackedSegmentLib : uses note for DiscreteCurveMathLib_v1 "Operates on PackedSegment data" DiscreteCurveMathLib_v1 ..> PackedSegment : uses note for DiscreteCurveMathLib_v1 "References error definitions from the interface" DiscreteCurveMathLib_v1 ..> IDiscreteCurveMathLib_v1 : uses (errors) note for PackedSegmentLib "Creates and unpacks PackedSegment types" PackedSegmentLib ..> PackedSegment : manipulates note for PackedSegmentLib "References error definitions from the interface for validation" PackedSegmentLib ..> IDiscreteCurveMathLib_v1 : uses (errors)Base Contracts
DiscreteCurveMathLib_v1is a Solidity library and does not inherit from any base contracts. It is a standalone collection of functions.Key Changes to Base Contract
Not applicable, as this is a library, not an upgrade or modification of a base contract.
User Interactions
This library itself does not have direct user interactions with state changes. It provides pure functions to be used by other contracts (e.g., a Funding Manager). Below are examples of how a calling contract might use this library.
Example: Calculating Purchase Return
A Funding Manager (FM) contract would use
_calculatePurchaseReturnto determine how many issuance tokens a user receives for a given amount of collateral.Preconditions (for the FM, not the library call itself):
PackedSegmentdata defining the curve.currentTotalIssuanceSupplyof its token.FM calls
_calculatePurchaseReturnfrom the library:The FM passes its segment data, the user's
collateralAmountIn, and thecurrentTotalIssuanceSupplyto the library function.FM uses the result:
The FM would then use
issuanceAmountOutandcollateralAmountSpentto handle the token transfers (take collateral, mint issuance tokens).Sequence Diagram (Conceptual for FM using the Library)
sequenceDiagram participant User participant FM as Funding Manager participant Lib as DiscreteCurveMathLib_v1 participant CT as Collateral Token participant IT as Issuance Token User->>FM: buyTokens(collateralAmountIn, minIssuanceOut) FM->>Lib: _calculatePurchaseReturn(segments, collateralAmountIn, currentSupply) Lib-->>FM: issuanceAmountOut, collateralSpent FM->>FM: Check issuanceAmountOut >= minIssuanceOut FM->>CT: transferFrom(User, FM, collateralSpent) FM->>IT: mint(User, issuanceAmountOut) FM-->>User: Success/TokensDeployment
Preconditions
DiscreteCurveMathLib_v1is a library. It is not deployed as a standalone contract instance that holds state or requires ownership. Libraries are typically linked by other contracts during their compilation/deployment.A contract intending to use this library would need:
PackedSegmentdata, correctly configured and validated, representing its desired bonding curve. This data would typically be initialized in the constructor or a setup function of the consuming contract.Deployment Parameters
Not applicable. Libraries do not have constructors or
init()functions in the same way contracts do.Deployment
The library's code is included in contracts that use it. When a contract using
DiscreteCurveMathLib_v1is compiled and deployed:internal, the library code is embedded directly into the consuming contract's bytecode.publicorexternalfunctions (whichDiscreteCurveMathLib_v1does not, for its core logic), it might need to be deployed separately and linked, but this is not the case here for its primary usage pattern.Deployment of contracts using this library would follow standard Inverter Network procedures:
Setup Steps
Not applicable for the library itself. A contract using this library (e.g., a Funding Manager) would require setup steps to define its curve segments. This typically involves:
initialPrice_,priceIncrease_,supplyPerStep_,numberOfSteps_) need to be determined. While an off-chain script or helper might use a struct similar toSegmentConfigfor convenience, the library's_createSegmentfunction takes these as individual arguments.DiscreteCurveMathLib_v1._createSegment()for each set of parameters to get thePackedSegmentbytes32 value.PackedSegmentLib(used by_createSegment) will validate individual parameters.PackedSegment[]array in the consuming contract's state.PackedSegment[]array usingDiscreteCurveMathLib_v1._validateSegmentArray()to check for array-level properties likeMAX_SEGMENTSand correct inter-segment price progression.The NatSpec comments within
DiscreteCurveMathLib_v1.solandIDiscreteCurveMathLib_v1.solprovide details on function parameters and errors, which would be relevant for developers integrating this library.FM_BC_Discrete_Redeeming_VirtualSupply_v1
Purpose of Contract
This contract serves as a Funding Manager (FM) module for the Inverter Protocol. It implements a discrete bonding curve with redeeming capabilities, allowing users to buy (mint) and sell (redeem) an issuance token against a collateral token. The curve's price points are defined by an array of packed segments. A key feature is its management of a virtual collateral supply, which is used in conjunction with the actual issuance token supply for price calculations and curve operations. It also incorporates a fee mechanism, including fixed project fees and cached protocol fees.
Glossary
To understand the functionalities of the following contract, it is important to be familiar with the following definitions.
FeeManagercontract. These are cached by this FM during initialization.Implementation Design Decision
The purpose of this section is to inform the user about important design decisions made during the development process. The focus should be on why a certain decision was made and how it has been implemented.
PackedSegmentfor Efficiency: Curve segments are defined usingPackedSegmentstructs, which utilize bit-packing (viaDiscreteCurveMathLib_v1which internally usesPackedSegmentLib.solprinciples) to store segment parameters gas-efficiently on-chain.DiscreteCurveMathLib_v1for Calculations: All core mathematical operations for the bonding curve (calculating purchase/sale returns, finding positions on the curve, validating segments) are delegated to theDiscreteCurveMathLib_v1library. This separation enhances modularity, testability, and auditability.virtualCollateralSupply. This value is updated with the net collateral added or removed during buy/sell operations. It, along with theissuanceToken.totalSupply(), serves as a crucial input for theDiscreteCurveMathLib_v1to determine current price points and calculate transaction outcomes. This decouples the mathematical model slightly from the instantaneous physical balance for certain calculations, especially relevant forgetStaticPriceForBuying.FeeManagercontract once during initialization (__FM_BC_Discrete_Redeeming_VirtualSupply_v1_Init) and stored in the_protocolFeeCachestruct. The overridden_getFunctionFeesAndTreasuryAddressesfunction then serves these cached values for internal calls duringcalculatePurchaseReturn,calculateSaleReturn,_buyOrder, and_sellOrder.PROJECT_BUY_FEE_BPS,PROJECT_SELL_FEE_BPS) and are set during initialization. These are retrieved via the overridden_getBuyFee()and_getSellFee()internal functions.VirtualCollateralSupplyBase_v1,RedeemingBondingCurveBase_v1,Module_v1) to reuse common functionalities and adhere to the Inverter module framework.Inheritance
UML Class Diagramm
classDiagram direction RL class ERC165Upgradeable { <<abstract>> } class Module_v1 { <<abstract>> +init(IOrchestrator_v1, Metadata, bytes) #_getFunctionFeesAndTreasuryAddresses() } class BondingCurveBase_v1 { <<abstract>> +buyFee +issuanceToken +buyFor(address,uint,uint) +calculatePurchaseReturn(uint) +getStaticPriceForBuying() uint #_issueTokensFormulaWrapper(uint) uint #_handleCollateralTokensBeforeBuy(address,uint) #_handleIssuanceTokensAfterBuy(address,uint) #_getBuyFee() uint } class RedeemingBondingCurveBase_v1 { <<abstract>> +sellFee +sellTo(address,uint,uint) +calculateSaleReturn(uint) +getStaticPriceForSelling() uint #_redeemTokensFormulaWrapper(uint) uint #_handleCollateralTokensAfterSell(address,uint) #_getSellFee() uint } class VirtualCollateralSupplyBase_v1 { <<abstract>> +virtualCollateralSupply +getVirtualCollateralSupply() uint +setVirtualCollateralSupply(uint) #_setVirtualCollateralSupply(uint) #_addVirtualCollateralAmount(uint) #_subVirtualCollateralAmount(uint) } class IFundingManager_v1 { <<interface>> +token() IERC20 +transferOrchestratorToken(address,uint) } class IFM_BC_Discrete_Redeeming_VirtualSupply_v1 { <<interface>> +getSegments() PackedSegment[] +reconfigureSegments(PackedSegment[]) +ProtocolFeeCache } class FM_BC_Discrete_Redeeming_VirtualSupply_v1 { -_token: IERC20 -_segments: PackedSegment[] -_protocolFeeCache: ProtocolFeeCache +PROJECT_BUY_FEE_BPS +PROJECT_SELL_FEE_BPS +init(IOrchestrator_v1, Metadata, bytes) +supportsInterface(bytes4) bool +token() IERC20 +getIssuanceToken() address +getSegments() PackedSegment[] +getStaticPriceForBuying() uint +getStaticPriceForSelling() uint +buyFor(address,uint,uint) +sellTo(address,uint,uint) +transferOrchestratorToken(address,uint) +setVirtualCollateralSupply(uint) +reconfigureSegments(PackedSegment[]) +calculatePurchaseReturn(uint) uint +calculateSaleReturn(uint) uint #_getFunctionFeesAndTreasuryAddresses() #_getBuyFee() uint #_getSellFee() uint #_setIssuanceToken(ERC20Issuance_v1) #_setSegments(PackedSegment[]) #_setVirtualCollateralSupply(uint) #_redeemTokensFormulaWrapper(uint) uint #_handleCollateralTokensAfterSell(address,uint) #_handleCollateralTokensBeforeBuy(address,uint) #_handleIssuanceTokensAfterBuy(address,uint) #_issueTokensFormulaWrapper(uint) uint } Module_v1 <|-- BondingCurveBase_v1 BondingCurveBase_v1 <|-- RedeemingBondingCurveBase_v1 ERC165Upgradeable <|-- VirtualCollateralSupplyBase_v1 Module_v1 <|-- VirtualCollateralSupplyBase_v1 IFM_BC_Discrete_Redeeming_VirtualSupply_v1 <|.. FM_BC_Discrete_Redeeming_VirtualSupply_v1 IFundingManager_v1 <|.. FM_BC_Discrete_Redeeming_VirtualSupply_v1 VirtualCollateralSupplyBase_v1 <|-- FM_BC_Discrete_Redeeming_VirtualSupply_v1 RedeemingBondingCurveBase_v1 <|-- FM_BC_Discrete_Redeeming_VirtualSupply_v1 note for FM_BC_Discrete_Redeeming_VirtualSupply_v1 "Manages a discrete bonding curve with redeeming and virtual collateral supply."Base Contracts
The contract
FM_BC_Discrete_Redeeming_VirtualSupply_v1inherits from:IFM_BC_Discrete_Redeeming_VirtualSupply_v1: Interface specific to this contract.IFundingManager_v1: Standard interface for Funding Manager modules. ([Link to IFundingManager_v1 docs - Placeholder])VirtualCollateralSupplyBase_v1: Abstract contract providing logic for managing a virtual collateral supply. ([Link to VirtualCollateralSupplyBase_v1 docs - Placeholder])RedeemingBondingCurveBase_v1: Abstract contract providing base functionalities for a bonding curve that supports redeeming. This itself inherits fromBondingCurveBase_v1andModule_v1. ([Link to RedeemingBondingCurveBase_v1 docs - Placeholder])Functions that have been overridden to adapt functionalities are outlined below.
Key Changes to Base Contract
The purpose of this section is to highlight which functions of the base contract have been overridden and why.
supportsInterface(bytes4 interfaceId): Overridden fromRedeemingBondingCurveBase_v1andVirtualCollateralSupplyBase_v1to includetype(IFM_BC_Discrete_Redeeming_VirtualSupply_v1).interfaceIdandtype(IFundingManager_v1).interfaceIdin the check, in addition to callingsuper.supportsInterface(interfaceId).init(IOrchestrator_v1 orchestrator_, Metadata memory metadata_, bytes memory configData_): Overridden fromModule_v1to decodeconfigData_(issuance token address, collateral token address, initial segments) and call__FM_BC_Discrete_Redeeming_VirtualSupply_v1_Initfor specific initialization.__FM_BC_Discrete_Redeeming_VirtualSupply_v1_Init(...): Internal initializer that sets up issuance token, collateral token, initial segments, project fees, and caches protocol fees.token(): ImplementsIFundingManager_v1to return the collateral token (_token).getIssuanceToken(): Overridden fromBondingCurveBase_v1andIBondingCurveBase_v1to return the address of theissuanceToken.getStaticPriceForBuying(): Overridden fromBondingCurveBase_v1,IBondingCurveBase_v1, andIFM_BC_Discrete_Redeeming_VirtualSupply_v1. Implemented to find the price on the curve forvirtualCollateralSupply + 1using_segments._findPositionForSupply.getStaticPriceForSelling(): Overridden fromRedeemingBondingCurveBase_v1andIFM_BC_Discrete_Redeeming_VirtualSupply_v1. Implemented to find the price on the curve forissuanceToken.totalSupply()using_segments._findPositionForSupply.buyFor(address _receiver, uint _depositAmount, uint _minAmountOut): Overridden fromBondingCurveBase_v1andIBondingCurveBase_v1. Calls_buyOrderand then updates thevirtualCollateralSupplyby adding the net collateral received.sellTo(address _receiver, uint _depositAmount, uint _minAmountOut): Overridden fromRedeemingBondingCurveBase_v1. Calls_sellOrderand then updates thevirtualCollateralSupplyby subtracting the total collateral paid out.setVirtualCollateralSupply(uint virtualSupply_): Overridden fromVirtualCollateralSupplyBase_v1andIFM_BC_Discrete_Redeeming_VirtualSupply_v1to beonlyOrchestratorAdminand calls_setVirtualCollateralSupply._setVirtualCollateralSupply(uint virtualSupply_): Overridden fromVirtualCollateralSupplyBase_v1to callsuper._setVirtualCollateralSupply.calculatePurchaseReturn(uint _depositAmount): Overridden fromBondingCurveBase_v1andIBondingCurveBase_v1. Calculates the net mint amount after deducting cached protocol fees (collateral and issuance side) and project buy fees from collateral. Uses_issueTokensFormulaWrapperfor the gross calculation.calculateSaleReturn(uint _depositAmount): Overridden fromRedeemingBondingCurveBase_v1. Calculates the net redeem amount after deducting cached protocol fees (issuance and collateral side) and project sell fees from collateral. Uses_redeemTokensFormulaWrapperfor the gross calculation._getFunctionFeesAndTreasuryAddresses(bytes4 functionSelector_): Overridden fromModule_v1. Returns cached protocol fees and treasury addresses from_protocolFeeCachefor buy/sell related function selectors, otherwise defers tosuper._getBuyFee(): Overridden fromBondingCurveBase_v1to return the constantPROJECT_BUY_FEE_BPS._getSellFee(): Overridden fromRedeemingBondingCurveBase_v1to return the constantPROJECT_SELL_FEE_BPS._issueTokensFormulaWrapper(uint _depositAmount): Implements the abstract function fromBondingCurveBase_v1. Uses_segments._calculatePurchaseReturnfromDiscreteCurveMathLib_v1._redeemTokensFormulaWrapper(uint _depositAmount): Implements the abstract function fromRedeemingBondingCurveBase_v1. Uses_segments._calculateSaleReturnfromDiscreteCurveMathLib_v1._handleCollateralTokensBeforeBuy(address _provider, uint _amount): Implements the abstract function fromBondingCurveBase_v1. Transfers collateral from_providerto the contract._handleIssuanceTokensAfterBuy(address _receiver, uint _amount): Implements the abstract function fromBondingCurveBase_v1. Mints issuance tokens to_receiver._handleCollateralTokensAfterSell(address _receiver, uint _collateralTokenAmount): Implements the abstract function fromRedeemingBondingCurveBase_v1. Transfers collateral to_receiver.User Interactions
The purpose of this section is to highlight common user interaction flows, specifically those that involve a multi-step process. Please note that only the user interactions defined in this contract should be listed. Functionalities inherited from base contracts should be referenced accordingly.
Function:
buyFor(Buy Issuance Tokens)To execute a buy operation (mint issuance tokens by depositing collateral):
Precondition
BondingCurveBase_v1)._receiveraddress must be valid (not address(0)).msg.senderif they are the provider) must have sufficient collateral tokens.minAmountOut(Recommended):To protect against slippage, the minimum amount of issuance tokens expected can be pre-computed.
buyForFunction:solidity // User wants to buy for themselves address receiver = msg.sender; fm.buyFor(receiver, collateralTokenAmountToDeposit, minIssuanceTokensOut);Sequence Diagram
sequenceDiagram participant User participant FM_BC_Discrete as FM_BC_Discrete_Redeeming_VirtualSupply_v1 participant CollateralToken as Collateral Token (IERC20) participant IssuanceToken as Issuance Token (ERC20Issuance_v1) participant DiscreteMathLib as DiscreteCurveMathLib_v1 participant FeeManager as Fee Manager (via super call in init) User->>FM_BC_Discrete: buyFor(receiver, depositAmount, minAmountOut) FM_BC_Discrete->>FM_BC_Discrete: _buyOrder(...) FM_BC_Discrete->>CollateralToken: safeTransferFrom(user, this, depositAmount) Note over FM_BC_Discrete: Calculate net deposit after project & protocol collateral fees (using cached fees) FM_BC_Discrete->>DiscreteMathLib: _calculatePurchaseReturn(netDeposit, currentSupply) DiscreteMathLib-->>FM_BC_Discrete: grossTokensToMint Note over FM_BC_Discrete: Calculate net tokensToMint after protocol issuance fees (using cached fees) FM_BC_Discrete->>IssuanceToken: mint(receiver, netTokensToMint) Note over FM_BC_Discrete: Update projectCollateralFeeCollected Note over FM_BC_Discrete: Transfer protocol collateral fees to treasury (cached address) Note over FM_BC_Discrete: Mint protocol issuance fees to treasury (cached address) FM_BC_Discrete->>FM_BC_Discrete: _addVirtualCollateralAmount(netCollateralAdded) FM_BC_Discrete-->>User: (implicit success or revert)Function:
sellTo(Sell Issuance Tokens)To execute a sell operation (redeem issuance tokens for collateral):
Precondition
RedeemingBondingCurveBase_v1)._receiveraddress must be valid.msg.senderif they are the provider) must have sufficient issuance tokens.minAmountOut(Recommended):To protect against slippage, the minimum amount of collateral tokens expected can be pre-computed.
sellToFunction:solidity // User wants to sell and receive collateral themselves address receiver = msg.sender; fm.sellTo(receiver, issuanceTokenAmountToDeposit, minCollateralTokensOut);Sequence Diagram
sequenceDiagram participant User participant FM_BC_Discrete as FM_BC_Discrete_Redeeming_VirtualSupply_v1 participant IssuanceToken as Issuance Token (ERC20Issuance_v1) participant CollateralToken as Collateral Token (IERC20) participant DiscreteMathLib as DiscreteCurveMathLib_v1 participant FeeManager as Fee Manager (via super call in init) User->>IssuanceToken: approve(FM_BC_Discrete, issuanceTokenAmountToDeposit) User->>FM_BC_Discrete: sellTo(receiver, issuanceTokenAmountToDeposit, minAmountOut) FM_BC_Discrete->>FM_BC_Discrete: _sellOrder(...) FM_BC_Discrete->>IssuanceToken: burnFrom(user, netIssuanceDepositAfterProtocolFee) Note over FM_BC_Discrete: Calculate net issuance deposit after protocol issuance fees (using cached fees) FM_BC_Discrete->>DiscreteMathLib: _calculateSaleReturn(netIssuanceDeposit, currentSupply) DiscreteMathLib-->>FM_BC_Discrete: grossCollateralToReturn Note over FM_BC_Discrete: Calculate net collateralToReturn after project & protocol collateral fees (using cached fees) FM_BC_Discrete->>CollateralToken: safeTransfer(receiver, netCollateralToReturn) Note over FM_BC_Discrete: Update projectCollateralFeeCollected Note over FM_BC_Discrete: Transfer protocol collateral fees to treasury (cached address) Note over FM_BC_Discrete: Mint protocol issuance fees to treasury (cached address) FM_BC_Discrete->>FM_BC_Discrete: _subVirtualCollateralAmount(totalCollateralTokenMovedOut) FM_BC_Discrete-->>User: (implicit success or revert)Function:
reconfigureSegments(Admin Interaction)Allows an
orchestratorAdminto change the bonding curve's segment configuration.Precondition
orchestratorAdminrole (enforced byonlyOrchestratorAdminmodifier).Steps
PackedSegment[] memory newSegments_.fm.reconfigureSegments(newSegments_).Important Note: The function includes an invariance check:
newSegments_._calculateReserveForSupply(issuanceToken.totalSupply())must equalvirtualCollateralSupply. If not, the transaction reverts. This ensures the new curve configuration is consistent with the current backing.Sequence Diagram
sequenceDiagram participant Admin participant FM_BC_Discrete as FM_BC_Discrete_Redeeming_VirtualSupply_v1 participant DiscreteMathLib as DiscreteCurveMathLib_v1 participant IssuanceToken as Issuance Token (ERC20Issuance_v1) Admin->>FM_BC_Discrete: reconfigureSegments(newSegments) FM_BC_Discrete->>IssuanceToken: totalSupply() IssuanceToken-->>FM_BC_Discrete: currentIssuanceSupply FM_BC_Discrete->>DiscreteMathLib: _calculateReserveForSupply(newSegments, currentIssuanceSupply) DiscreteMathLib-->>FM_BC_Discrete: newCalculatedReserve alt Invariance Check Passes (newCalculatedReserve == virtualCollateralSupply) FM_BC_Discrete->>FM_BC_Discrete: _setSegments(newSegments) FM_BC_Discrete-->>Admin: Event: SegmentsSet else Invariance Check Fails FM_BC_Discrete-->>Admin: Revert: InvarianceCheckFailed endDeployment
Preconditions
The following preconditions must be met before deployment:
IOrchestrator_v1).ERC20Issuance_v1(to allow the FM to mint tokens).IERC20Metadata).Deployment Parameters
The
initfunction is called by the Orchestrator during module registration. TheconfigData_bytes argument must be ABI encoded with the following parameters:( address issuanceTokenAddress, // Address of the ERC20Issuance_v1 token address collateralTokenAddress, // Address of the IERC20Metadata collateral token PackedSegment[] memory initialSegments // The initial array of packed segments for the curve )Example (conceptual encoding):
abi.encode(0xIssuanceToken, 0xCollateralToken, initialSegmentsArray)The list of deployment parameters can also be found in the Technical Reference section of the documentation under the
init()function ([link - Placeholder for NatSpec link]).Deployment
Deployment should be done using one of the methods provided below:
Setup Steps
initcall, which configures tokens, segments, and fees.orchestratorAdminmight need to:setVirtualCollateralSupply(initialSupply)if the curve needs an initial virtual collateral amount not established through initial buys.After deployment, the mandatory and optional setup steps can be found in the contract NatSpec ([link - Placeholder for NatSpec link]).