PI-13: Dynamic Convert Up Bonus + Optimizations
Executed on October 23, 2025 at 01:13 UTC.
Summary
- Dynamic Convert Up Bonus: Implement a Dynamic Convert Up Bonus with a dynamic Convert Up Bonus per PDV Converted and a dynamic Convert Up Bonus Capacity. Add signed Grown Stalk slippage controls to Converts.
- Seed Gauge Points: Normalize the Seed Gauge Points every Season.
- Morning Auction: Change the Temperature every block during the Morning Auction.
- Soil Demand: Update the minimum Sown Pinto threshold to measure demand for Soil to be a function of the initial Soil issued.
- Till Scalar: Update the Till Scalar to dynamically adjust as a function of Pod Rate.
- Tractor Signatures: Upgrade Tractor to support smart contracts.
- Convert Penalty: Update the Generalized Convert Penalty such that it properly penalizes Converts that are negative to peg maintenance.
- Well Valuation: Normalize Well liquidity units and guard price calculations for very small pools.
- Deposit PDV Return: Correct the PDV returned by Deposits.
Links
- Implementation Commit:
090536b7b271528c0f18903fbea4730f06d06a43
Problems
1. Dynamic Convert Up Bonus
Despite the 200% Crop Ratio, there was an insufficient incentive to Convert up, leading the protocol to spend excessive time below its value target.
2. Seed Gauge Points
The effect of the changes in Gauge Points from Season to Season fluctuated with the total number of Gauge Points. This decreased the accuracy with which Pinto can adjust the Seeds between Whitelisted Deposited LP tokens.
3. Morning Auction
The Temperature during the Morning Auction did not increase continuously throughout the Auction, leading to more Pod issuance than needed.
4. Soil Demand
A static threshold (25 Soil) to measure demand for Soil made Pinto less resilient to Soil Demand manipulation at larger supply.
5. Till Scalar
Soil issued above the value target abruptly increased/decreased when the Pod Rate was close to the Pod Rate thresholds.
6. Rain and Flood
It may be less healthy for it to Flood at lower L2SRs than at higher L2SRs.
7. Tractor Signatures
Smart contracts were unable to utilize Tractor.
8. Deposit Approvals
Silo Deposits did not properly adhere to the ERC1155 token standard.
9. Convert Penalty
The implementation of the Pipeline Convert Penalty was mistakenly over-penalizing certain Converts.
10. Well Valuation
The time-weighted Well liquidity getter returned values in the non-Pinto token’s native decimal precision. The shared Well price calculation also lacked a minimum Pinto-reserve guard, thereby allowing it to calculate prices for very small pools.
11. Deposit PDV Return
The Deposit function returned raw Stalk in the slot documented as PDV, so callers could misinterpret the value of a Deposit even though its stored accounting used the calculated PDV.
Solutions
1. Dynamic Convert Up Bonus
Implement a Dynamic Convert Up Bonus that works similarly to the Cultivation System – which has demonstrated efficacy with a similar problem in the Field (elaborated in Rationale) – to create a one-time Stalk bonus, realized immediately upon Convert. The Convert Up Bonus per PDV is dynamic, and fluctuates based on the demand for Converts over the past two Seasons. The Convert Up Bonus Capacity, measured in PDV, changes similarly to how the Soil supply changes in the Field. The bonus applies to Well LP → Pinto Converts, is capped by the Deposit’s available Grown Stalk, and uses capacity that ramps to its Season maximum over the first half of the Season. The gauge resets the bonus and maximum capacity to zero when TWAΔP is positive. The new Convert variants allow callers to require a minimum net Grown Stalk result, including a minimum bonus. For more info see Dynamic Convert Bonus and Penalty System: Fine-Tuned Convert Incentives.
2. Seed Gauge Points
Normalize the Seed Gauge Points across Whitelisted LP tokens to target a total of 10,000 every Season, subject to integer rounding.
3. Morning Auction
Upgrade the Morning Auction to change the Temperature based on elapsed time, observable every block, over 600 seconds with a floor of 1% of the Maximum Temperature, subject to fixed-point rounding.
4. Soil Demand
Update the threshold to measure demand for Soil to be min(initial Soil, max(50 Pinto, 5% of initial Soil)).
5. Till Scalar
Set the Till Scalar to decrease linearly from 1.2 at a 3% Pod Rate to 0.25 at 25%, clamped at those endpoints, for above-target and mixed-sign Soil issuance.
6. Rain and Flood
Update the criteria for Pinto to Rain/Flood to be TWAΔP > 0, Pod Rate < 3%, and L2SR >= 40%.
7. Tractor Signatures
Update the Tractor system to be ERC-1271 compatible.
8. Deposit Approvals
Update Silo Deposits to properly support the ERC1155 global approval standard.
9. Convert Penalty
Update the Pipeline Convert Penalty to take the maximum of the penalty for Converting beyond the value target and the penalty for Converting more than the instantaneous DeltaP, capped at the PDV Converted.
10. Well Valuation
Normalize the time-weighted Well liquidity getter to 18 decimal places. Return a zero price when the reserves used by the shared Well price calculation contain fewer than 10 Pinto.
11. Deposit PDV Return
Return the calculated PDV in the Deposit function’s PDV output while retaining its existing token-count and stem outputs.
Rationale
1. Dynamic Convert Up Bonus
The Cultivation System in the Field has demonstrated efficacy at creating an efficient market for Soil and Temperature. For the Dynamic Convert Up Bonus, the need to offer a Convert Up Bonus Capacity that generally meets demand, and offer a reasonable Stalk Bonus per PDV, is a very similar problem (i.e., amount, rate). Thus, a very similar mechanism has been created for the Silo.
Changing the Convert Up Bonus per PDV dynamically based on demand over the past two Seasons – decreasing the Bonus when demand increases or at least 95% of capacity is used, increasing it when demand decreases and Pinto’s Seed rate is higher than the highest LP Seed rate, and otherwise keeping it constant – will allow the protocol to discover the Bonus per PDV at which there is demand to Convert.
By changing the Convert Up Bonus Capacity to meet demand for Converts up, the protocol will minimize the instances where there is a significant amount of unused Capacity. This should minimize the disincentive to Convert by avoiding instances where there is significant unused Convert Up Bonus Capacity.
2. Seed Gauge Points
Normalizing the Seed Gauge Points every Season, subject to integer rounding, will result in a similar magnitude of changes in Seeds from Season to Season across long periods of time. This will keep the protocol’s response to changes in state more consistent.
3. Morning Auction
By jumping the Temperature during the Morning Auction after a number of blocks, the protocol was paying a higher interest rate than it needed to to participants in the Morning Auction. By changing the Temperature every block, the protocol reduces the gaps between offered Temperatures when attracting lenders.
4. Soil Demand
Scaling the minimum Sown threshold with initial Soil makes the cost of registering demand grow with the available supply. Capping the threshold at initial Soil keeps it reachable in Seasons with fewer than 50 Soil initially available.
5. Till Scalar
Interpolating between the existing endpoint scalars removes abrupt changes in Soil issuance at Pod Rate boundaries. Higher Pod Rates still reduce Soil issuance. The same interpolation applies to above-target issuance and Soil issuance when TWAΔP is nonpositive but instantaneous ΔP is positive, preserving the response to debt while smoothing its transitions.
6. Rain and Flood
When Pinto Floods at lower L2SRs, Pinto risks short term yield farmers exiting the protocol, decreasing the L2SR further. It may be healthier for the system to continue growing without a Flood to stunt momentum until the L2SR is high enough to sustain large exits without compromising long term sustainability.
7. Tractor Signatures
Smart wallets can use ERC-1271 contract signatures to authorize Tractor requisitions. By allowing smart contracts to utilize Tractor, smart wallets are able to interact with the Pinto protocol more easily.
8. Deposit Approvals
By supporting ERC1155 global approvals, Silo Deposits adhere to the ERC1155 Standard.
9. Convert Penalty
Pinto penalizes Converts when the Convert moves Pinto away from its value target, and when the number of tokens Converted exceeds the DeltaP of the Well(s). When a Deposit is penalized, the maximum penalty is equal to the number of Grown Stalk the Deposit has (per PDV). When a farmer Converted past the value target, they were effectively penalized twice (once for moving Pinto away from the target, and another for Converting beyond deltaP), which could double-count the overlapping penalized PDV. Given that the maximum number that Pinto could ever penalize per PDV is the number of Grown Stalk associated with that PDV (as any penalty beyond that would mean that a farmer would lose less Grown Stalk by manually Withdrawing and Depositing LP tokens rather than Converting), the penalty should be capped at 100% of the Grown Stalk of the Converted assets.
10. Well Valuation
Normalizing liquidity to a common precision makes getter results comparable across non-Pinto tokens with different decimals. Returning zero for a Well with fewer than 10 Pinto reserves avoids presenting a price calculation from a pool too small for the protocol’s minimum-reserve criterion.
11. Deposit PDV Return
Returning the calculated PDV ensures that a contract calling the Deposit function receives the Deposit’s value in Pinto, rather than the number of Stalk minted. This corrects the external result without changing the Deposit’s Stalk minting calculation.
Contract Changes
Initialization
Executed InitPI13.init(address,uint256) at 0x59816de18d73690d30c6b9de2375c34b2ef7e7e7, reading key 0 from 0xbe95c6806ab4eefc10ac9e96938c3a222964dd91. Set maxTotalGaugePoints to 10000e18 and normalized existing LP Gauge Points. Added the Convert Up Bonus gauge with initial maximum capacity 0, capacity factor 1%, tracked deficit 16,702.835 Pinto, and stored bonus-per-PDV value 2027670666 (0.2027670666 Stalk per PDV). Set the Morning duration to 600 seconds and morningControl to floor(1e18 / 240).
Facet Changes
The cut replaced 233 existing selectors and added 11 selectors. It removed no selectors. ApprovalFacet and BDVFacet had import-only changes. ClaimFacet and OracleFacet retained their own source files but were redeployed with updated dependencies. Replacing a facet did not change every function in it.
Other Contract / Parameter Changes
Externally linked libraries are listed below. Every listed library address changed. LibGerminate, LibShipping and LibWellMinting retained their own source files, although their compiled dependencies may differ. The LibIncentive branch relocation preserved its reward outputs. The old LibSilo and LibTokenSilo deployments varied by consuming facet.
The new bonus gauge used target-Season bounds of 250–1,000, a minimum maximum capacity of 50 PDV, and capacity-factor adjustment bounds of 0.5–1.5 percentage points before inversion for decreases. The factor was bounded between 1% and 100% when adjusted. Its tracked use, prior-Season use and last-used bonus began at 0. Existing Pod Rate bounds (3% and 25%), L2SR bounds (5%, 40% and 80%) and Till Scalar endpoints (1.2 and 0.25) were retained. The Soil-demand calculation replaced the fixed 25-Pinto threshold with min(initialSoil, max(50e6, floor(initialSoil * 0.05e6 / 1e6))).
Function Changes
- Added
convertUpBonusGauge(bytes,bytes,bytes)and sharedLibGaugeHelperslogic. Positive TWAΔP resets maximum capacity, bonus, factor and tracked deficit to 0. Otherwise the gauge tracks the largest deficit, classifies capacity use at the inclusive 80% and 95% thresholds, and compares bonus-eligible PDV use across two Seasons with inclusive 95%–105% steady demand. With no prior use, positive current use is increasing demand and zero current use is decreasing demand. It rolls current use into prior use and resets current use each Season. Gauge-call failure retains the prior stored value and data. AddedcaseIdtoLibEvaluate.BeanstalkState, which is encoded as gaugesystemData.evaluateBeanstalk()now returns that struct, and the Season helpers consume its case identifier.
- Calculated maximum capacity from the tracked deficit, capacity factor and a clamped target of 1,000 Seasons at 3% Pod Rate to 250 at 25%, with a 50-PDV minimum in the nonpositive-TWAΔP branch. Filled capacity increases the factor. Below 80% use, it decreases when demand is not decreasing or the current bonus is at least the last-used bonus. Other cases retain the factor. The last-used bonus is updated when capacity use is at least 80% and demand is not decreasing. The increase step interpolates from 0.5 to 1.5 percentage points over L2SR 5%–80%. The decrease step is its reciprocal in the implemented fixed-point units. Bonus changes use the difference between Pinto’s Seed rate and the highest LP Seed rate when that difference is nonnegative, otherwise 1% of the prior bonus. Increasing demand or filled capacity reduces the bonus toward 0. Decreasing demand increases it only when the Seed-rate difference is positive. Other cases retain it.
- Added
LibConvert.stalkBonus()for Well LP → Pinto Converts. Available capacity ramps linearly to the maximum over half a Season. Eligible PDV is limited by remaining capacity. The bonus is capped by available Grown Stalk, and capacity use is recalculated with floor division when that cap binds. A zero bonus or exhausted capacity returns zero. The applying helper only credits a bonus when recalculated capacity use is positive, then stores used capacity before redepositing.
- Added
convertWithStalkSlippage()andpipelineConvertWithStalkSlippage(). SharedapplyStalkModifiersAndDeposit()applies the bonus or Convert Down Penalty, then checks net Grown Stalk against the original Deposit before redepositing. Signed slippage uses1e18for 100%. Zero requires no net loss, positive values allow a proportional loss, and negative values require a proportional gain. Values at least 100% accept any result. Insufficient Grown Stalk reverts the transaction, including capacity updates. Existingconvert()uses zero slippage andpipelineConvert()uses 100%. All four return stem, input/output token counts and input/output PDV.
- Added
getConvertStalkPerBdvBonusAndMaximumCapacity()for stored bonus and maximum capacity. AddedgetConvertStalkPerBdvBonusAndRemainingCapacity()for stored bonus and time-ramped remaining capacity, floored at 0. AddedgetCalculatedBonusStalkPerBdv()for the stored bonus andstalkBonus(uint256,uint256)for quoted capacity use and Grown Stalk gained. The quote does not update storage.
- Updated
LibGauge.updateGaugePoints()to cap each active LP’s pre-normalization points at twice its share of optimal Deposited PDV multiplied by the configured total Gauge Points, then normalize tomaxTotalGaugePointswith floor division. The implementation does not impose the minimum mentioned in its source comment. Missing oracle data, no Deposited LP PDV, or zero total candidate points skip the relevant update. A single LP uses the configured total directly. AddedgetMaxTotalGaugePoints()andgetSeedsForToken(address). The latter returns rawstalkEarnedPerSeason. Removed the fixed1000e18upper cap fromGaugeDefault.defaultGaugePointFunction()when points increase. ThegetSeedGauge()return tuple now includesmaxTotalGaugePoints.
- Updated
LibDibbler.morningTemperature()to calculateTmax * log2(1 + c*t) / log2(1 + c*600)before 600 seconds, withc = floor(1e18/240)/1e18and an integer floor ofTmax/100. At or after 600 seconds it returns the Maximum Temperature. Earlier-than-Sunrise timestamps use elapsed time 0. Replaced the prior block-stepped calculation and absolute 1% Temperature floor.LibWeathernow casts updates to the wideneduint64Temperature field. WidenedWeather.tempfromuint32touint64. Theweather()return tuple now includesmorningControlandmorningDuration, with its reserved buffer reduced from four slots to three.
- Updated
LibEvaluate.calcDeltaPodDemand()through the new internalcalcMinSoilDemandThreshold(). Below the dynamic threshold, it returns zero demand and resets the current sow-time marker. Equality proceeds to the existing timing/demand logic. AddedbeanSown()for current-Season Sown Pinto andgetOrderLockedBeans()for stored order-locked Pinto, both in raw six-decimal units. RemovedminSoilSownDemandfrom thegetExtEvaluationParameters()return tuple and expanded its reserved buffer from 60 slots to 61.
- Updated
Sun.scaleSoilAbovePeg()from the four Pod Rate bands to clamped linear interpolation from 1.2 at 3% to 0.25 at 25%. Above-target Soil first divides newly Harvestable Pods by one plus the Maximum Temperature. The same scalar also applies to the 1%-of-absolute-TWAΔP branch when TWAΔP is nonpositive and instantaneous ΔP is positive. The fully nonpositive branch retains its separate liquidity/Cultivation Factor logic. SharedLibGaugeHelpers.linearInterpolation()now permits equal output endpoints and returns their constant value. Invalid or equal input endpoints still revert.
- Updated
LibFlood.handleRain()to require the relatively-high-or-higher liquidity case in addition to positive TWAΔP and Pod Rate below 3%. At the verified parameters this requires L2SR ≥ 40%. Losing any condition ends Rain. Starting Rain and continuing into Flood retain the existing timing and distribution logic. The removed duplicate guard incalculateSopPerWell()did not change behavior.
- Updated
TractorFacet.verifyRequisitionto callIERC1271.isValidSignature()for publishers with deployed code and require0x1626ba7e. Publishers without code retain ECDSA recovery. Invalid hashes, signatures or failed contract validation revert. EIP-7702 alone does not guarantee that a delegated account implements ERC-1271.
- Updated shared
LibSilo._spendDepositAllowance()to bypass per-token allowance spending for the owner and globally approved operators. Other spenders still require sufficient per-token allowance, with unlimited allowance unchanged.transferDeposits()andTokenSilo._transferDeposit()now always invoke this common check.
- Updated
LibConvert.applyStalkPenalty()to usemin(max(againstPegPenalty, convertCapacityPenalty), bdvConverted)rather than adding overlapping penalties.LibPipelineConvert.checkForValidConvertAndUpdateConvertCapacity()now returns proportionally retained Grown Stalk using output PDV instead of requiring zero penalty. Net slippage is checked afterward. The input-Well post-convert ΔP calculation uses 0 when LP supply becomes 0, avoiding that division by zero.
- Updated
getTwaLiquidityForWell(address)to multiply the existing result by1e18and divide by10 ** tokenDecimals. Updated sharedLibWell.calculateTokenBeanPriceFromReserves()to return 0 below10e6Pinto reserves before calculating the one-Pinto price increment. The zero result is a price sentinel, not a fallback to a prior price.
- Corrected
SiloFacet.deposit()so its second return value is PDV rather than raw Stalk.LibTokenSilo.deposit()now returns PDV alongside Stalk and Germination side, and internalTokenSilo._deposit()passes PDV through while still minting the calculated Germinating Stalk. The external selector and three-value return shape remain unchanged.
Event Changes
- Added
ConvertUpBonus(account,grownStalkGained,newGrownStalk,bdvCapacityUsed,bdvConverted), emitted only when the applying helper credits positive bonus Grown Stalk with positive capacity use. Added input and output PDV fields toConvertin both Convert facets, changing its event signature.
- Used existing
AddedGaugeandUpdatedGaugeValueduring bonus-gauge initialization,UpdatedGaugeDatawhen capacity is consumed, andEngaged/EngagedDataduring Season updates. These existing event signatures did not change.
- Added
UpdateMaxTotalGaugePoints(uint256), emitted with10000e18during initialization.GaugePointChangenow reports normalized points in the multi-LP update and was also emitted for initialization. The single-LP branch updates storage without emitting that event.
- Retained the
RainStatusevent signature, with emissions following the stricter Rain condition.
- Changed
ConvertDownPenaltyemission to require positive lost Grown Stalk. Its fields and signature are unchanged.