Exp: `DiscreteCurveMathLib_v1 ` & `FM_BC_Discrete_Redeeming_VirtualSupply_v1`
DiscreteCurveMathLib_v1
Purpose of Contract
The DiscreteCurveMathLib_v1 is 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.
| Definition | Explanation |
|---|---|
| PP | Payment Processor module, typically handles queued payment operations. |
| FM | Funding Manager type module, which would utilize this library for its bonding curve calculations. |
| Issuance Token | Tokens that are distributed (minted/burned) by a Funding Manager contract, often based on the calculations provided by this library. |
| Discrete Bonding Curve | A bonding curve where the price of the issuance token changes at discrete intervals (steps) rather than continuously. |
| Segment | A distinct portion of the discrete bonding curve, defined by its own set of parameters: an initial price, a price increase per step, a supply amount per step, and a total number of steps. |
| Step | The smallest unit within a segment where a specific quantity of issuance tokens (supplyPerStep) can be bought or sold at a fixed price. |
| PackedSegment | A custom Solidity type (type PackedSegment is bytes32;) used by this library to store all four parameters of a curve segment into a single bytes32 value. This optimizes storage gas costs. |
PackedSegmentLib |
A helper library (located in ../libraries/PackedSegmentLib.sol), imported by DiscreteCurveMathLib_v1, responsible for the creation, validation, packing, and unpacking of PackedSegment data. |
Scaling Factor (1e18) |
A constant (10^18) used for fixed-point arithmetic to handle decimal precision for prices and token amounts, assuming standard 18-decimal tokens. |
MAX_SEGMENTS |
A constant (10) defining the maximum number of segments a curve configuration can have, enforced by functions like _validateSegmentArray and _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_v1 is 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 single bytes32 slot using the custom type PackedSegment and the internal helper library PackedSegmentLib.
-
Why: Storing an array of segments for a bonding curve can be gas-intensive if each segment's parameters occupy separate storage slots. By packing all parameters into one
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. -
How:
PackedSegmentLibdefines the bit allocation for each parameter within thebytes32value, along with masks and offsets. It provides:- A
createfunction that validates input parameters against their bit limits and packs them. - Accessor functions (
initialPrice,priceIncrease, etc.) to retrieve individual parameters. - An
unpackfunction to retrieve all parameters at once. ThePackedSegmenttype itself (beingbytes32) ensures type safety, preventing accidental mixing with otherbytes32values that do not represent curve segments.
- A
Segment Validation Rules
To ensure economic sensibility and robustness, DiscreteCurveMathLib_v1 and its helper PackedSegmentLib enforce specific validation rules for segment configurations:
-
No Free Segments (
PackedSegmentLib._create): Segments that are entirely "free" – meaning theirinitialPriceis 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 usingDiscreteCurveMathLib_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:- A "True Flat" segment must have
numberOfSteps == 1andpriceIncreasePerStep == 0. Attempting to create a multi-step flat segment (e.g.,numberOfSteps > 1andpriceIncreasePerStep == 0) will revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__InvalidFlatSegment(). - A "True Sloped" segment must have
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. ThepriceIncreasePerStepbeinguint256inherently prevents price decreases within a single segment.
- A "True Flat" segment must have
Other Important Validation Rules & Errors:
-
No Segments Configured (
_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(). -
Too Many Segments (
_validateSegmentArray,_calculateReserveForSupply): If the providedsegments_array exceedsMAX_SEGMENTS(currently 10), relevant functions will revert withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__TooManySegments(). -
Supply Exceeds Curve Capacity (
_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). -
Zero Collateral Input (
_calculatePurchaseReturn): Attempting a purchase with zero collateral reverts withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__ZeroCollateralInput(). -
Zero Issuance Input (
_calculateSaleReturn): Attempting a sale with zero issuance tokens reverts withIDiscreteCurveMathLib_v1.DiscreteCurveMathLib__ZeroIssuanceInput(). -
Insufficient Issuance to Sell (
_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 the IDiscreteCurveMathLib_v1.sol interface file for the contracts to compile and function correctly.
Efficient Calculation Methods
To further optimize gas for on-chain computations:
-
Arithmetic Series for Reserve/Cost Calculation: For sloped segments (where
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. -
Direct Iteration for Purchase Calculation: The
_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. -
Optimized Sale Calculation: The
_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 PackedSegment offers 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 initialPrice and priceIncrease fields currently use 72 bits each.
- Maximum value:
2^72 - 1(approx.4.722 x 10^21) wei. - For a standard 18-decimal token, this translates to a maximum representable price of approx.
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:
- The maximum dollar value that can be represented for
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).
- To represent $1.00 worth of this token, one would need
1 / $0.0000000001 = 10,000,000,000tokens. - In wei (18 decimals):
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:
- Using collateral tokens with fewer decimals (e.g., 6 or 8, like many stablecoins) significantly increases the nominal range.
- Protocols can restrict collateral to tokens that fit reasonably within the existing bit allocation.
-
Price Scaling Factor:
- The bonding curve logic (in the consuming contract) could implement an additional scaling factor for prices. For example, a
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.
- The bonding curve logic (in the consuming contract) could implement an additional scaling factor for prices. For example, a
-
Alternative Bit Allocation in
PackedSegment:- A future version of the library or a different packing scheme could allocate more bits to price fields (e.g., 96 bits) at the expense of bits for supply or by using more than one
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.
- A future version of the library or a different packing scheme could allocate more bits to price fields (e.g., 96 bits) at the expense of bits for supply or by using more than one
-
Protocol-Level Policies:
- Collateral Whitelisting: Enforce requirements on collateral tokens, such as minimum price or maximum effective decimals, to ensure compatibility.
- Dynamic Configuration: Allow curve deployers to specify bit allocations or scaling factors per curve instance, though this adds complexity.
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. The using 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_v1 is 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 _calculatePurchaseReturn to determine how many issuance tokens a user receives for a given amount of collateral.
Preconditions (for the FM, not the library call itself):
- The FM has access to the array of
PackedSegmentdata defining the curve. - The FM knows the
currentTotalIssuanceSupplyof its token. - The user (caller of the FM) has sufficient collateral and has approved it to the FM.
-
FM calls
_calculatePurchaseReturnfrom the library: The FM passes its segment data, the user'scollateralAmountIn, and thecurrentTotalIssuanceSupplyto the library function.// In a Funding Manager contract // import {DiscreteCurveMathLib_v1, PackedSegment} from ".../DiscreteCurveMathLib_v1.sol"; // // PackedSegment[] internal _segments; // IERC20 public _issuanceToken; // Assume it has a totalSupply() // IERC20 public _collateralToken; function getPurchaseReturn(uint256 collateralAmountIn) public view returns (uint256 issuanceAmountOut, uint256 collateralAmountSpent) { // This is a simplified example. A real FM would get _segments from storage. // PackedSegment[] memory currentSegments = _segments; // If _segments is storage array // For this example, assume segments are passed or constructed. PackedSegment[] memory segments = new PackedSegment[](1); // Example segments segments[0] = DiscreteCurveMathLib_v1._createSegment(1e18, 0.1e18, 10e18, 100); // uint256 currentTotalIssuanceSupply = _issuanceToken.totalSupply(); // Get current supply // For example purposes, let's assume a current supply uint256 currentTotalIssuanceSupply = 50e18; (issuanceAmountOut, collateralAmountSpent) = DiscreteCurveMathLib_v1._calculatePurchaseReturn( segments, collateralAmountIn, currentTotalIssuanceSupply ); } -
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/Tokens
Deployment
Preconditions
DiscreteCurveMathLib_v1 is 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:
- An array of
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_v1 is compiled and deployed:
- If all library functions are
internal, the library code is embedded directly into the consuming contract's bytecode. - If the library had
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:
- Manual deployment: Through Inverter Network's Control Room application.
- SDK deployment: Through Inverter Network's TypeScript SDK or React SDK.
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:
-
Preparing Segment Data: For each segment, the individual parameters (
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. -
Creating PackedSegments: Iterating through the prepared segment data and calling
DiscreteCurveMathLib_v1._createSegment()for each set of parameters to get thePackedSegmentbytes32 value.PackedSegmentLib(used by_createSegment) will validate individual parameters. -
Storing PackedSegments: Storing the resulting
PackedSegment[]array in the consuming contract's state. -
Validating Segment Array: Validating the entire
PackedSegment[]array usingDiscreteCurveMathLib_v1._validateSegmentArray()to check for array-level properties likeMAX_SEGMENTSand correct inter-segment price progression.
The NatSpec comments within DiscreteCurveMathLib_v1.sol and IDiscreteCurveMathLib_v1.sol provide 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.
| Definition | Explanation |
|---|---|
| FM | Funding Manager type module, responsible for managing the issuance and redemption of tokens, and holding collateral. |
| Issuance Token | The ERC20 token minted by this FM when users deposit collateral (e.g., $HOUSE token). |
| Collateral Token | The ERC20 token users deposit to buy issuance tokens, or receive when selling issuance tokens (e.g., a stablecoin). |
| PackedSegment | A struct that efficiently stores the parameters of a single segment of the discrete bonding curve (initial price, price increase per step, supply per step, number of steps). |
| Discrete Bonding Curve (DBC) | A bonding curve where the price changes in discrete steps rather than continuously. |
| Virtual Collateral Supply | A state variable representing the total collateral that should be backing the issuance tokens according to the curve's state, used for calculations. It's updated during buy/sell operations. |
| Protocol Fee | Fees defined by the broader protocol/orchestrator, managed by a FeeManager contract. These are cached by this FM during initialization. |
| Project Fee | Fees specific to this FM instance, currently hardcoded as constants for buy and sell operations. |
| Orchestrator | The central contract that manages and coordinates different modules within a workflow. |
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.
- Discrete Bonding Curve Model: The contract employs a discrete bonding curve, where prices are defined by a series of segments, and within each segment, by discrete steps. This provides predictable price points and allows for flexible curve shapes.
-
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. -
Virtual Collateral Supply: The contract maintains a
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. -
Protocol Fee Caching: To optimize gas usage and reduce external calls, protocol fees (both collateral and issuance side, for buy and sell operations) and their respective treasury addresses are fetched from the
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. -
Fixed Project Fees: Project-specific fees for buy and sell operations are currently implemented as hardcoded constants (
PROJECT_BUY_FEE_BPS,PROJECT_SELL_FEE_BPS) and are set during initialization. These are retrieved via the overridden_getBuyFee()and_getSellFee()internal functions. -
Modular Inheritance: The contract inherits from several base contracts (
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_v1 inherits 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
- Buying must be enabled (inherited from
BondingCurveBase_v1). - The
_receiveraddress must be valid (not address(0)). - The caller (or
msg.senderif they are the provider) must have sufficient collateral tokens. - The caller must have approved the FM contract to spend their collateral tokens.
-
Get
minAmountOut(Recommended): To protect against slippage, the minimum amount of issuance tokens expected can be pre-computed.// Assuming 'fm' is an instance of FM_BC_Discrete_Redeeming_VirtualSupply_v1 // and 'collateralTokenAmountToDeposit' is the amount of collateral tokens the user wants to spend. uint collateralTokenAmountToDeposit = 1000 * 10**18; // Example: 1000 collateral tokens uint minIssuanceTokensOut = fm.calculatePurchaseReturn(collateralTokenAmountToDeposit); // Apply a slippage tolerance if desired, e.g., minIssuanceTokensOut = minIssuanceTokensOut * 99 / 100; (1% slippage) -
Call
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
- Selling must be enabled (inherited from
RedeemingBondingCurveBase_v1). - The
_receiveraddress must be valid. - The caller (or
msg.senderif they are the provider) must have sufficient issuance tokens. - The caller must have approved the FM contract to spend their issuance tokens.
-
Get
minAmountOut(Recommended): To protect against slippage, the minimum amount of collateral tokens expected can be pre-computed.// Assuming 'fm' is an instance of FM_BC_Discrete_Redeeming_VirtualSupply_v1 // and 'issuanceTokenAmountToDeposit' is the amount of issuance tokens the user wants to sell. uint issuanceTokenAmountToDeposit = 500 * 10**18; // Example: 500 issuance tokens uint minCollateralTokensOut = fm.calculateSaleReturn(issuanceTokenAmountToDeposit); // Apply a slippage tolerance if desired -
Call
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 orchestratorAdmin to change the bonding curve's segment configuration.
Precondition
- Caller must have the
orchestratorAdminrole (enforced byonlyOrchestratorAdminmodifier).
Steps
- The admin prepares a new array of
PackedSegment[] memory newSegments_. - The admin calls
fm.reconfigureSegments(newSegments_).
Important Note: The function includes an invariance check: newSegments_._calculateReserveForSupply(issuanceToken.totalSupply()) must equal virtualCollateralSupply. 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
end
Deployment
Preconditions
The following preconditions must be met before deployment:
- A deployed Orchestrator contract (
IOrchestrator_v1). - A deployed Issuance Token contract that implements
ERC20Issuance_v1(to allow the FM to mint tokens). - A deployed Collateral Token contract (
IERC20Metadata). - A deployed Fee Manager contract, configured with appropriate fees and treasury for this FM's orchestrator and module address.
Deployment Parameters
The init function is called by the Orchestrator during module registration. The configData_ 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:
- Manual deployment: Through Inverter Network's Control Room application.
- SDK deployment: Through Inverter Network's TypeScript SDK or React SDK.
Setup Steps
- The primary setup occurs during the
initcall, which configures tokens, segments, and fees. - After deployment, the
orchestratorAdminmight need to:- Call
setVirtualCollateralSupply(initialSupply)if the curve needs an initial virtual collateral amount not established through initial buys. - Configure other related modules or permissions in the Orchestrator if necessary.
- Call
After deployment, the mandatory and optional setup steps can be found in the contract NatSpec ([link - Placeholder for NatSpec link]).