PI-15: Beanstalk Repayments and Bug Fixes
Executed on March 6, 2026 at 16:58 UTC.
Summary
- Beanstalk Repayments: Add separate repayment routes for Beanstalk's Field, Silo and Barn obligations, with transferable positions, contract-account claims and accounting hooks for Pinto shipments and Internal Balance transfers.
- Convert Accounting and Penalties: Fix capacity double-counting and normalize the Grown Stalk penalty to simulated liquidity impact.
- Referral Eligibility: Preserve a former delegate's independently earned eligibility when referral delegation changes, and reject delegation to the zero address.
- Plot Management: Add bounded queries of Plot indexes and combine adjacent Plots to simplify position management.
Links
- Original Reference: PI-15 Github writeup.
- Implementation PR: PR #175.
- Diamond Cut Transaction: Base execution.
- Implementation Commit: 7b63656a0653ede85a72111a6967c01e6127895a.
Background
Pinto committed at launch to start repaying Beanstalk’s obligations after supply exceeds 1 billion Pinto. The original writeup specifies Arbitrum block 276160746 for the holder snapshot, with Silo allocations based on fully recapitalized Unripe value, equivalent Field positions and Barn Fertilizer balances.
Problems
1. Beanstalk Repayments
The contracts and accounting needed to repay Beanstalk’s obligations had not yet been implemented.
2. Convert Accounting and Penalties
The per-block Convert capacity used to calculate Grown Stalk penalties tracks movement toward peg across all Wells and within each Well. Its accounting counted earlier Converts again with each new Convert in the same block, overstating capacity usage.
Grown Stalk penalties had two calculation problems. Overall movement toward or away from peg was measured from the spot reserve imbalance immediately before the Convert, making the result sensitive to preceding trades. The resulting penalty amount was then treated directly as Pinto Denominated Value (PDV) subject to Grown Stalk removal, rather than scaled to the Convert’s size and simulated effect on pool liquidity.
3. Referral Eligibility
Removing delegation from an account cleared the former delegate's eligibility, even when that account had independently Sown enough Pinto to qualify. The function also permitted delegation to the zero address.
4. Plot Management
The Field interface lacked bounded queries for positions in an account's Plot-index list and a way to combine adjacent Plots.
Solutions
1. Beanstalk Repayments
Add separate repayment positions for Beanstalk’s Field, Silo and Barn obligations, with transfers and claims on Base.
Under the normal allocation, 48.5% of newly minted Pinto goes to the regular Silo and 48.5% to the regular Field. The remaining 3% shifts from the budget to Beanstalk repayments as supply crosses 1 billion Pinto: initially 1% each to the repayment Field, Silo and Barn. Once the Barn is repaid, the repayment Field and Silo each receive 1.5%; once the Silo is repaid, the repayment Field receives 3%.
- Field: Represent claims as transferable Plots in a separate repayment Field. Holders can transfer their positions and harvest Pinto as their Pods become harvestable.
- Silo: Represent claims as transferable
urBDVtokens, which earn Pinto repayments whether held in a wallet or Internal Balance. Earned Pinto stays with the account that earned it when tokens are transferred; future repayments follow the tokens. Claims can be paid to a chosen recipient’s wallet or Internal Balance. Holders must retain a positiveurBDVbalance to claim, so they should claim earned Pinto before transferring their entire balance.
- Barn: Represent claims as transferable Fertilizer positions. Holders can claim repaid Pinto to their wallet or Internal Balance. Before Fertilizer moves, any accrued Pinto rewards for the sender and recipient are paid to each person’s own Internal Balance. The Fertilizer position and its future repayment rights then pass to the recipient.
- Contract accounts: Eligible contracts that can act from their own address on Base can claim directly and choose where to receive their positions. At launch, contracts that needed to choose a receiver from Ethereum could not do so: the messenger targeted the wrong Base address. A separate upgrade is planned to restore that route. A contract receiving Fertilizer must support ERC-1155 transfers.
2. Convert Accounting and Penalties
Count each Convert’s use of the per-block, overall and per-Well capacity once, preserving the correct remaining capacity for later Converts in the same block. This capacity governs Grown Stalk penalties and is separate from Convert Up bonus capacity.
Make the overall penalty less sensitive to trades immediately before a Convert by measuring its effect against a reserve baseline that limits sudden changes. Individual Well checks still use spot reserves.
Scale Grown Stalk penalties to the proportion of a Convert that moves away from peg or exceeds remaining capacity. The penalty reflects the Convert’s size and expected liquidity impact, rather than treating the raw reserve imbalance as an amount of deposit value to penalize. At most 100% of the output PDV is subject to this penalty.
Apply these changes to ordinary and batch Pinto ↔ Well LP Converts and Pipeline Converts. Same-token Converts receive no penalty from this calculation. Existing Grown Stalk minimums, directional bonuses or penalties, and slippage protections still apply; invalid reserve or oracle data can prevent a Convert.
3. Referral Eligibility
When changing a delegate, clear the former delegate's eligibility only if their own recorded Sowing is below the eligibility threshold. Reject a zero-address delegate.
4. Plot Management
Add getters for Plot-index list length, a Plot's stored reverse index, selected array positions and half-open index ranges. Position queries preserve the requested order. Range queries require a nonempty range within the list bounds.
Add combinePlots for at least two positive caller-owned Plots supplied in ascending, exactly adjacent Pod Line order. Aggregate their Pods into the first Plot, remove later Plots from account indexing, and cancel listings for those later Plots. The first Plot's listing remains. Index removal uses swap-and-pop, so account-list order is not preserved.
Rationale
1. Beanstalk Repayments
Give Beanstalk holders a way to receive the repayments Pinto committed to at launch. Transferable positions let holders pass future repayment rights to someone else, while keeping track of Pinto already earned. Separate Field, Silo and Barn positions preserve the different claims holders had in Beanstalk. Repayment limits prevent paying more than is owed or using more newly minted Pinto than allocated.
2. Convert Accounting and Penalties
Later Converts should not face higher Grown Stalk penalties because earlier Converts were counted more than once. Correcting that accounting preserves the capacity actually available in each block.
Penalties should reflect how much a Convert moves Pinto away from peg or exceeds available capacity. Making the calculation less sensitive to preceding trades and scaling it to the Convert’s size makes the penalty better reflect that Convert’s effect.
3. Referral Eligibility
Delegation changes should preserve eligibility earned through an account’s own Sowing. Rejecting the zero address prevents assigning it referral eligibility.
4. Plot Management
Combining Plots simplifies management without moving their Pod Line position or creating Pods.
Contract Changes
At Base block 43,013,477, the cut added 14 selectors, replaced 197 and removed three. The source merged March 10, after execution. This inventory excludes standalone Blueprint deployments in that merge.
Initialization
Executed InitBeanstalkShipments through init(ShipmentRoute[],address). It replaced shipment routes, called addField, set Field 1's pods to 919768387056514 raw units (919,768,387.056514 Pods at six decimals), and registered the supplied SiloPayback's protocolUpdate(address,address,uint256) hook with encoding type 0x00. It assumes the newly added Field is Field 1. It does not explicitly reset harvestable or harvested, and account Plot population is separate.
Removed the three temporary population selectors. The populator-only initializeRepaymentPlots wrote quantities and index mappings in batches without guarding against repeated entries. Historical receipts confirm that 62 successful batches populated all 7,598 Plots exactly once, matching the population dataset. Two repeated attempts reverted.
Facet Changes
The previous implementations below are verified against historical routing at Base block 43,013,476, immediately before PI-15. All 197 replaced selectors match the listed addresses, all 14 added selectors were absent, and the three removed selectors pointed to TempRepaymentFieldFacet. PI-15 was the only Diamond upgrade in its execution block.
ConvertGettersFacet adds the new penalty overload and replaces 15 other selectors. This cut leaves the prior penalty selector untouched.
Other Contract / Parameter Changes
Runtime link offsets verify these libraries, including indirect LibTokenSilo. PipelineConvertFacet uses the new libraries despite stale compiler settings. Previous dependencies belong to the previous facets verified against pre-PI-15 routing.
LibReceiving is internal to the shipping path, not a separate external link.
Repayment deployment sets. Constructor records verify initial implementations and administrators, not proxy storage or application ownership at execution.
| Set and contract | Deployed address | Initial implementation | Initial proxy admin | Deployment block |
|---|---|---|---|---|
| PR Silo | 0xBa0534B16017E861bA15f1587D8A060e15F7f336 | 0xCebbDb437330a2f9049A4882918762Bc1A5fB582 | 0xf71f4d69d8ad7eb6dcfb1c8ac36fdda3d057500d | 42043042 |
| PR Barn | 0x724c87b9A6Ea6BA7A7CF3bfc0309DE778fB28027 | 0xd5a8E2FF523Cb3dB614A669c4044E908Ebaad041 | 0xf71f4d69d8ad7eb6dcfb1c8ac36fdda3d057500d | 42043046 |
| PR distributor | 0xB0e3DC9066C7aFC90065008e8d7a3c6Cb1F66B37 | Not applicable: direct deployment | Not applicable | 42043050 |
| Executed-route Silo | 0x525C94754C51946a7a3B72580Ce0DF36922E1E64 | 0xDbb10C0cE795FFd3A4003CF0EcC849b25a788EB1 | 0xE5A707d49968937C860762fB256CE9dC7B1370F0 | 42062562 |
| Executed-route Barn | 0x68bDbb0402a3Ca89C7C4af8e41C021635102d158 | 0x5bb2b891496F9f4db1467755eDc240329EA08E2C | 0xE5A707d49968937C860762fB256CE9dC7B1370F0 | 42062570 |
| Executed-route set distributor | 0xbA941Af3292c49b585f4EC5C8164c1dfc893EEdC | 0xED3Ead9D8b6E57f666E60ccf833f3ed1Be2Dd4c0 | 0xE5A707d49968937C860762fB256CE9dC7B1370F0 | 42062579 |
The reference lists L1ContractMessenger at 0xD2abd9a7E7F10e3bF4376fb03A07fca729A55b6f. Its deployed bytecode matches the source, with 50 fixed whitelisted callers. Its immutable Base destination, 0x1152691C30aAd82eB9baE7e32d662B19391e34Db, had no contract code at PI-15 execution and differs from the distributor above. This messenger therefore could not register a receiver in the PI-15 distributor as configured.
Function Changes
- Added separate repayment plan getters returning
(points,cap). UpdatedsetShipmentRoutesto normalize zero planner addresses to the Diamond before storage andShipmentRoutesSetemission. Added Silo/Barn receiving callbacks. A callback failure skips its transfer, while a later token-transfer failure or an invalid Field receipt can revert the outer operation.
- Added
SiloPaybackreceipt, reward-checkpoint, claim and balance getters. Added Barn repayment and Fertilizer claim/transfer accounting. Explicit Silo and Barn claims use the active Tractor publisher when present, otherwise the caller. Added direct contract-account claims and authenticated L1 receiver-record assignment. Receiver assignment requires caller0x4200000000000000000000000000000000000007and cross-domain sender0xD2abd9a7E7F10e3bF4376fb03A07fca729A55b6f. It can overwrite the receiver’s record and requires a separate claim.
- Added pre-transfer hook registration/encoding/call helpers and
hasTokenHook/getTokenHookgetters. Registration requires nonzero token, target and selector values, deployed target code and a successful test call with zero-valued arguments. The production facet's external hook-administration methods are commented out.
- Updated shared capacity and penalty logic for ordinary, batch and Pipeline Converts through the new linked implementations. Added a
calculateStalkPenaltyoverload with finalfromAmountargument, selector0x9752afb3. The old selector0xb325d2efis not removed or replaced by this cut. Historical routing confirms the old selector remained at0x334a110ad41fd417d4c8c3f82d9c85a3850192beimmediately before and after PI-15. The six-signed-integer input tuple retains its types and order. Its final two entries now represent capped and shadow overall deltaB. Of the four unchanged return types, the last three report current-call capacity deltas. The new simulation requires both initial and resulting Pinto reserves to be at least 10 Pinto, valid USD-oracle ratios at lookback zero, and successful Well Function calls.
- Updated
delegateReferralRewardsto preserve independently earned former-delegate eligibility and reject zero-address delegation. The threshold is unchanged.
- Added the four Plot-index query helpers and
combinePlots. Moved Plot insertion into internalLibDibbler.insertPlotfor shared use. A raw reverse-index lookup is not an existence check, and removed indexes receivetype(uint256).max.
Event Changes
- Added repayment-position minting and reward events, including
UnripeBdvTokenMinted,SiloPaybackRewardsReceived, BarnPaybackRewardsReceivedandSiloPaybackRewardsClaimed. Earlier population transactions emitted oneRepaymentPlotAddedevent for each of the 7,598 Plots. Fertilizer claims useClaimFertilizer, and its transfers use ERC-1155 events. Each owner Fertilizer mint emits twoTransferSinglelogs, from the helper and caller, for one mint.
- Added
AddedTokenHookandTokenHookCalledon registration and successful calls. The internal removal helper emitsRemovedTokenHookif called. The PI-15 finalization transaction emitted no such event. ExistingReceiptuses the new Silo/Barn recipient values.Shippedrecords the shipping input, not a verified sum of every recipient's successful receipts.
- Added
PlotsCombined(account,fieldId,plotIndexes,totalPods). Canceling later Plot listings retains the existing cancellation events. No new Convert-capacity event or referral event is added by the reviewed source changes.