Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
e2db0f8
twap manipulation poc
pocikerim Jan 1, 2026
210c841
bdv manipulation
pocikerim Jan 5, 2026
d942fbf
auto-format: prettier formatting for Solidity files
actions-user Jan 5, 2026
24236da
fix: Shadow DeltaB and ratio-based penalty for convert manipulation r…
pocikerim Jan 11, 2026
6f4bc45
test: Update TWAPManipulation test from shadowdeltab-fix-logs branch
pocikerim Jan 11, 2026
3acfb38
PoC: bdv manipulation's effect on grownStalks
pocikerim Jan 12, 2026
8d79a70
prank fix and balance logs
pocikerim Jan 13, 2026
8087607
Merge 8d79a7043a28a246438564752a648cdcb1922c84 into dc0db81c10d0d6915…
pocikerim Jan 13, 2026
5da7b86
auto-format: prettier formatting for Solidity files
actions-user Jan 13, 2026
a86df4e
comment refactors
pocikerim Jan 13, 2026
a985a34
Merge branch 'fix/convert-manipulation' of https://github.com/pociker…
pocikerim Jan 13, 2026
343c781
fix stalkPenalty calculation
pocikerim Jan 13, 2026
6f81a10
Merge 343c7813a4ed2e1d415d60177d353e3941690793 into dc0db81c10d0d6915…
pocikerim Jan 13, 2026
aaff508
auto-format: prettier formatting for Solidity files
actions-user Jan 13, 2026
c72b836
comment refactors
pocikerim Jan 13, 2026
cd8d3cf
use calculateDeltaBFromReserves for accurate LP→Bean deltaB impact c…
pocikerim Jan 14, 2026
3f505bd
Merge branch 'fix/convert-manipulation' of https://github.com/pociker…
pocikerim Jan 14, 2026
d3df4c3
Merge 3f505bdf9284ef71daa809cd7d8b625e03bc8273 into dc0db81c10d0d6915…
pocikerim Jan 14, 2026
886a4d1
auto-format: prettier formatting for Solidity files
actions-user Jan 14, 2026
33b99a8
update tests calculateStalkPenalty calls with fromAmount parameter an…
pocikerim Jan 15, 2026
416b8ee
Merge 33b99a8e8681d0ebc8e2b35deb04c3522b7813f4 into dc0db81c10d0d6915…
pocikerim Jan 15, 2026
5b60c29
auto-format: prettier formatting for Solidity files
actions-user Jan 15, 2026
55306b7
flash loan manipulation tests
pocikerim Jan 15, 2026
897bad8
comment refactors
pocikerim Jan 15, 2026
9fdbe5d
Use liquidity based deltaB calculations for convert operations instea…
pocikerim Jan 16, 2026
8b0d0e1
Merge 9fdbe5da6eaaa78579b75074a34afc8f9b27ca09 into dc0db81c10d0d6915…
pocikerim Jan 16, 2026
ccdb22f
auto-format: prettier formatting for Solidity files
actions-user Jan 16, 2026
01d5c87
naming refactors
pocikerim Jan 16, 2026
3c0de9d
Merge branch 'fix/convert-manipulation' of https://github.com/pociker…
pocikerim Jan 16, 2026
d074c20
Merge 3c0de9d1e7959ae326c480eb0c5b119907abbf74 into dc0db81c10d0d6915…
pocikerim Jan 16, 2026
99b8ed9
auto-format: prettier formatting for Solidity files
actions-user Jan 16, 2026
aa74084
Remove try catch blocks in calculateMaxDeltaBImpact to revert on Well…
pocikerim Jan 16, 2026
1cc0cd7
comment refactors
pocikerim Jan 18, 2026
27d0436
Use wellIsOrWasSoppable for convert deltaB impact to support dewhitel…
pocikerim Jan 19, 2026
473a518
comment refactor
pocikerim Jan 19, 2026
9635996
naming refactor
pocikerim Jan 19, 2026
8c66e64
Add fork test for convert penalty resistance against spot oracle mani…
pocikerim Jan 21, 2026
dde9f29
Remove unnecessary _abs call in Well->Bean deltaB impact calculation
pocikerim Jan 21, 2026
9987a4c
Clarify deltaB impact calculation comments for Bean and LP input scen…
pocikerim Jan 21, 2026
ccf8fb6
Use cached twapOverallDeltaB instead of redundant overallCappedDeltaB…
pocikerim Jan 21, 2026
574ff17
Modify reserves array in place instead of creating unnecessary copy
pocikerim Jan 21, 2026
66c48a4
Replace silent returns with reverts for invalid LP removal edge cases
pocikerim Jan 21, 2026
5b8fa2a
use direct uint256 cast for Bean->Well deltaB calculation and remove …
pocikerim Jan 21, 2026
520149b
Merge 5b8fa2a9568abab189e3f0643daa0fadae38ef34 into dc0db81c10d0d6915…
pocikerim Jan 21, 2026
32dedb4
auto-format: prettier formatting for Solidity files
actions-user Jan 21, 2026
0fdb8f7
Refactor calculateMaxDeltaBImpact to deduplicate afterDeltaB calculation
pocikerim Jan 21, 2026
db1249c
Merge 0fdb8f798dccf2d4d30176f399576d484dfee125 into dc0db81c10d0d6915…
pocikerim Jan 21, 2026
de33794
auto-format: prettier formatting for Solidity files
actions-user Jan 21, 2026
acebbeb
Optimize convert gas by reusing pre-fetched deltaB and reserves acros…
pocikerim Jan 22, 2026
bd7a2d2
Merge acebbeba1b826b9592543d5c7eef493735795b11 into de337940d63013145…
pocikerim Jan 22, 2026
c1996d5
auto-format: prettier formatting for Solidity files
actions-user Jan 22, 2026
d120165
naming refactor
pocikerim Jan 22, 2026
c4f04c0
comment refactor
pocikerim Jan 22, 2026
75c9e82
Fix LAMBDA->LAMBDA pipeline converts to apply penalty when theoretica…
pocikerim Jan 23, 2026
ebdb630
Refactor calculateConvertCapacityPenalty to return targetWell and res…
pocikerim Jan 28, 2026
e9874f9
Merge branch 'fix/convert-manipulation' into fix/optimize-convert-system
pocikerim Jan 28, 2026
1e79eb3
Optimize DeltaB calculation by caching reserves and reusing across co…
Feb 11, 2026
395552b
Merge 1e79eb3d951892f50dd383c53a11e512d9893eef into 75c9e82705ed2cedd…
pocikerim Feb 11, 2026
fd311b4
auto-format: prettier formatting for Solidity files
actions-user Feb 11, 2026
e66594c
Merge pull request #2 from pocikerim/fix/optimize-convert-system
pocikerim Feb 13, 2026
49909f0
Add NatSpec for shadowOverallDeltaB
pocikerim Feb 14, 2026
fc32c84
Merge remote-tracking branch 'origin/frijo/release/PI-15' into fix/co…
pocikerim Feb 14, 2026
6e95fd6
Fix double-convert test to preserve capped reserves across spot manip…
pocikerim Feb 14, 2026
8ce4301
Sync MockPump after Well state changes in Hardhat tests
pocikerim Feb 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions contracts/beanstalk/facets/silo/ConvertGettersFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ contract ConvertGettersFacet {
* @return deltaB The capped reserves deltaB for the well
*/
function cappedReservesDeltaB(address well) external view returns (int256 deltaB) {
return LibDeltaB.cappedReservesDeltaB(well);
(deltaB, ) = LibDeltaB.cappedReservesDeltaB(well);
}

/**
Expand Down Expand Up @@ -110,8 +110,9 @@ contract ConvertGettersFacet {
*/
function getWellConvertCapacity(address well) external view returns (uint256) {
AppStorage storage s = LibAppStorage.diamondStorage();
(int256 deltaB, ) = LibDeltaB.cappedReservesDeltaB(well);
return
LibConvert.abs(LibDeltaB.cappedReservesDeltaB(well)).sub(
LibConvert.abs(deltaB).sub(
s.sys.convertCapacity[block.number].wellConvertCapacityUsed[well]
);
}
Expand All @@ -125,7 +126,8 @@ contract ConvertGettersFacet {
uint256 bdvConverted,
uint256 overallConvertCapacity,
address inputToken,
address outputToken
address outputToken,
uint256 fromAmount
)
external
view
Expand All @@ -142,7 +144,8 @@ contract ConvertGettersFacet {
bdvConverted,
overallConvertCapacity,
inputToken,
outputToken
outputToken,
fromAmount
);
}

Expand Down
3 changes: 2 additions & 1 deletion contracts/beanstalk/facets/silo/abstract/ConvertBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ abstract contract ConvertBase is Invariable, ReentrancyGuard {
convertData,
cp.fromToken,
cp.toToken,
toBdv
toBdv,
cp.fromAmount
);

// if the Farmer is converting between beans and well LP, check for
Expand Down
25 changes: 17 additions & 8 deletions contracts/interfaces/IMockFBeanstalk.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ interface IMockFBeanstalk {
}

struct DeltaBStorage {
int256 beforeInputTokenDeltaB;
int256 afterInputTokenDeltaB;
int256 beforeOutputTokenDeltaB;
int256 afterOutputTokenDeltaB;
int256 beforeOverallDeltaB;
int256 afterOverallDeltaB;
int256 beforeInputTokenSpotDeltaB;
int256 afterInputTokenSpotDeltaB;
int256 beforeOutputTokenSpotDeltaB;
int256 afterOutputTokenSpotDeltaB;
int256 cappedOverallDeltaB;
int256 shadowOverallDeltaB;
}

struct Deposit {
Expand Down Expand Up @@ -744,7 +744,15 @@ interface IMockFBeanstalk {
uint256 inputTokenAmountInDirectionOfPeg,
address outputToken,
uint256 outputTokenAmountInDirectionOfPeg
) external view returns (uint256 cumulativePenalty, PenaltyData memory pdCapacity);
)
external
view
returns (
uint256 cumulativePenalty,
PenaltyData memory pdCapacity,
address targetWell,
uint256[] memory targetWellReserves
);

function calculateDeltaBFromReserves(
address well,
Expand All @@ -757,7 +765,8 @@ interface IMockFBeanstalk {
uint256 bdvConverted,
uint256 overallConvertCapacity,
address inputToken,
address outputToken
address outputToken,
uint256 fromAmount
)
external
view
Expand Down
120 changes: 90 additions & 30 deletions contracts/libraries/Convert/LibConvert.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,18 @@ library LibConvert {
uint256[] depositIds;
}

/**
* @param shadowOverallDeltaB Post-convert overall deltaB anchored to the capped baseline
* rather than raw spot values. Captures only the spot change caused by the convert,
* so pre-existing spot manipulation is neutralized.
*/
struct DeltaBStorage {
int256 beforeInputTokenDeltaB;
int256 afterInputTokenDeltaB;
int256 beforeOutputTokenDeltaB;
int256 afterOutputTokenDeltaB;
int256 beforeOverallDeltaB;
int256 afterOverallDeltaB;
int256 beforeInputTokenSpotDeltaB;
int256 afterInputTokenSpotDeltaB;
int256 beforeOutputTokenSpotDeltaB;
int256 afterOutputTokenSpotDeltaB;
int256 cappedOverallDeltaB;
int256 shadowOverallDeltaB;
}

struct PenaltyData {
Expand Down Expand Up @@ -192,7 +197,8 @@ library LibConvert {
uint256 bdvConverted,
uint256 overallConvertCapacity,
address inputToken,
address outputToken
address outputToken,
uint256 fromAmount
) internal returns (uint256 stalkPenaltyBdv) {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 overallCapacityDelta;
Expand All @@ -209,7 +215,8 @@ library LibConvert {
bdvConverted,
overallConvertCapacity,
inputToken,
outputToken
outputToken,
fromAmount
);

// Update penalties in storage.
Expand All @@ -235,7 +242,8 @@ library LibConvert {
uint256 bdvConverted,
uint256 overallConvertCapacity,
address inputToken,
address outputToken
address outputToken,
uint256 fromAmount
)
internal
view
Expand All @@ -256,7 +264,15 @@ library LibConvert {
spd.againstPeg.inputToken.add(spd.againstPeg.outputToken)
);

(spd.convertCapacityPenalty, spd.capacity) = calculateConvertCapacityPenalty(
// Get capacity penalty, target well, and reserves in one call
address targetWell;
uint256[] memory targetWellReserves;
(
spd.convertCapacityPenalty,
spd.capacity,
targetWell,
targetWellReserves
) = calculateConvertCapacityPenalty(
overallConvertCapacity,
spd.directionOfPeg.overall,
inputToken,
Expand All @@ -265,12 +281,26 @@ library LibConvert {
spd.directionOfPeg.outputToken
);

// Cap amount of bdv penalized at amount of bdv converted (no penalty should be over 100%)
stalkPenaltyBdv = min(
max(spd.higherAmountAgainstPeg, spd.convertCapacityPenalty),
bdvConverted
uint256 penaltyAmount = max(spd.higherAmountAgainstPeg, spd.convertCapacityPenalty);

uint256 pipelineConvertDeltaBImpact = LibDeltaB.calculateMaxDeltaBImpact(
inputToken,
fromAmount,
targetWell,
targetWellReserves
);

if (pipelineConvertDeltaBImpact > 0) {
// This scales the penalty proportionally to how much of the theoretical max was penalized
stalkPenaltyBdv = min(
(penaltyAmount * bdvConverted) / pipelineConvertDeltaBImpact,
bdvConverted
);
} else {
// L2L/AL2L converts have zero deltaB impact, resulting in zero penalty.
stalkPenaltyBdv = 0;
}

return (
stalkPenaltyBdv,
spd.capacity.overall,
Expand All @@ -280,14 +310,17 @@ library LibConvert {
}

/**
* @notice Calculates the convert capacity penalty and determines the target well for the conversion.
* @param overallCappedDeltaB The capped overall deltaB for all wells
* @param overallAmountInDirectionOfPeg The amount deltaB was converted towards peg
* @param inputToken Address of the input well
* @param inputToken Address of the input token
* @param inputTokenAmountInDirectionOfPeg The amount deltaB was converted towards peg for the input well
* @param outputToken Address of the output well
* @param outputToken Address of the output token
* @param outputTokenAmountInDirectionOfPeg The amount deltaB was converted towards peg for the output well
* @return cumulativePenalty The total Convert Capacity penalty, note it can return greater than the BDV converted
* @return pdCapacity The capacity deltas for overall, inputToken, and outputToken to add to storage
* @return targetWell The well involved in the convert (address(0) for L2L/AL2L converts)
* @return targetWellReserves The capped reserves for targetWell (empty for L2L/AL2L converts)
*/
function calculateConvertCapacityPenalty(
uint256 overallCappedDeltaB,
Expand All @@ -296,7 +329,16 @@ library LibConvert {
uint256 inputTokenAmountInDirectionOfPeg,
address outputToken,
uint256 outputTokenAmountInDirectionOfPeg
) internal view returns (uint256 cumulativePenalty, PenaltyData memory pdCapacity) {
)
internal
view
returns (
uint256 cumulativePenalty,
PenaltyData memory pdCapacity,
address targetWell,
uint256[] memory targetWellReserves
)
{
AppStorage storage s = LibAppStorage.diamondStorage();

ConvertCapacity storage convertCap = s.sys.convertCapacity[block.number];
Expand All @@ -316,8 +358,19 @@ library LibConvert {
// Return this convert's capacity usage for caller to add to storage
pdCapacity.overall = overallAmountInDirectionOfPeg;

// Calculate per-well capacity usage for caller to add to storage
// Determine target well. For L2L/AL2L (inputToken == outputToken), skip penalty calculation.
if (inputToken != outputToken) {
if (inputToken == s.sys.bean) {
targetWell = outputToken;
} else {
targetWell = inputToken;
}

// `targetWell` must be a well at this point.
(, targetWellReserves) = LibDeltaB.cappedReservesDeltaB(targetWell);
}

// Calculate per-well capacity usage for caller to add to storage
if (inputToken != s.sys.bean && inputTokenAmountInDirectionOfPeg > 0) {
(cumulativePenalty, pdCapacity.inputToken) = calculatePerWellCapacity(
inputToken,
Expand All @@ -343,7 +396,8 @@ library LibConvert {
uint256 cumulativePenalty,
ConvertCapacity storage convertCap
) internal view returns (uint256, uint256) {
uint256 tokenWellCapacity = abs(LibDeltaB.cappedReservesDeltaB(wellToken));
(int256 deltaB, ) = LibDeltaB.cappedReservesDeltaB(wellToken);
uint256 tokenWellCapacity = abs(deltaB);

uint256 cumulativeUsed = convertCap.wellConvertCapacityUsed[wellToken].add(
amountInDirectionOfPeg
Expand All @@ -361,11 +415,14 @@ library LibConvert {
function calculateAmountAgainstPeg(
DeltaBStorage memory dbs
) internal pure returns (PenaltyData memory pd) {
pd.overall = calculateAgainstPeg(dbs.beforeOverallDeltaB, dbs.afterOverallDeltaB);
pd.inputToken = calculateAgainstPeg(dbs.beforeInputTokenDeltaB, dbs.afterInputTokenDeltaB);
pd.overall = calculateAgainstPeg(dbs.cappedOverallDeltaB, dbs.shadowOverallDeltaB);
pd.inputToken = calculateAgainstPeg(
dbs.beforeInputTokenSpotDeltaB,
dbs.afterInputTokenSpotDeltaB
);
pd.outputToken = calculateAgainstPeg(
dbs.beforeOutputTokenDeltaB,
dbs.afterOutputTokenDeltaB
dbs.beforeOutputTokenSpotDeltaB,
dbs.afterOutputTokenSpotDeltaB
);
}

Expand Down Expand Up @@ -397,11 +454,14 @@ library LibConvert {
function calculateConvertedTowardsPeg(
DeltaBStorage memory dbs
) internal pure returns (PenaltyData memory pd) {
pd.overall = calculateTowardsPeg(dbs.beforeOverallDeltaB, dbs.afterOverallDeltaB);
pd.inputToken = calculateTowardsPeg(dbs.beforeInputTokenDeltaB, dbs.afterInputTokenDeltaB);
pd.overall = calculateTowardsPeg(dbs.cappedOverallDeltaB, dbs.shadowOverallDeltaB);
pd.inputToken = calculateTowardsPeg(
dbs.beforeInputTokenSpotDeltaB,
dbs.afterInputTokenSpotDeltaB
);
pd.outputToken = calculateTowardsPeg(
dbs.beforeOutputTokenDeltaB,
dbs.afterOutputTokenDeltaB
dbs.beforeOutputTokenSpotDeltaB,
dbs.afterOutputTokenSpotDeltaB
);
}

Expand All @@ -412,11 +472,11 @@ library LibConvert {
int256 beforeTokenDeltaB,
int256 afterTokenDeltaB
) internal pure returns (uint256) {
// Calculate absolute values of beforeInputTokenDeltaB and afterInputTokenDeltaB using the abs() function
// Calculate absolute values of beforeTokenDeltaB and afterTokenDeltaB using the abs() function
uint256 beforeDeltaAbs = abs(beforeTokenDeltaB);
uint256 afterDeltaAbs = abs(afterTokenDeltaB);

// Check if afterInputTokenDeltaB and beforeInputTokenDeltaB have the same sign
// Check if afterTokenDeltaB and beforeTokenDeltaB have the same sign
if (
(beforeTokenDeltaB >= 0 && afterTokenDeltaB >= 0) ||
(beforeTokenDeltaB < 0 && afterTokenDeltaB < 0)
Expand All @@ -426,7 +486,7 @@ library LibConvert {
// Return the difference between beforeDeltaAbs and afterDeltaAbs
return beforeDeltaAbs.sub(afterDeltaAbs);
} else {
// If afterInputTokenDeltaB is further from or equal to zero, return zero
// If afterTokenDeltaB is further from or equal to zero, return zero
return 0;
}
} else {
Expand Down
Loading