Summary
The MultiFlowPump._capRates
function calculates a ratio of cappedReserves
with its own formula instead of using the Well Function formula implementation received as parameter. This breaks the main idea of the composability support and can cause incorrect calculation for other well function implementations.
Vulnerability Details
According to the documentation Basin allows for anyone to compose new and existing (1) Well Functions (i.e. exchange functions), (2) Pumps (i.e., network native oracles) and (3) Well Implementations (i.e., exchange implementations) to create a Well (i.e., a customized liquidity pool).
and A Well Function defines an invariant relationship between a Well’s Reserves and the supply of Well LP tokens.
Though the MultiFlowPump
contract should use Well Functions for mentioned relationships everywhere there are two places where it does not.
When the _capRates
function caps the change in ratio of reserves
an in place calculation of a ratio of cappedReserves
is used in if
statements instead of using mfpWf.calcRate
.
function _capRates(
uint256[] memory lastReserves,
uint256[] memory reserves,
uint256 capExponent,
CapReservesParameters memory crp,
IMultiFlowPumpWellFunction mfpWf,
bytes memory data
) internal view returns (uint256[] memory cappedReserves) {
cappedReserves = reserves;
(uint256 i, uint256 j) = lastReserves[0] > lastReserves[1] ? (0, 1) : (1, 0);
CapRatesVariables memory crv;
crv.rLast = mfpWf.calcRate(lastReserves, i, j, data);
>> crv.r = mfpWf.calcRate(cappedReserves, i, j, data);
if (crv.r > crv.rLast) {
bytes16 tempExp = ABDKMathQuad.ONE.add(crp.maxRateChanges[i][j]).powu(capExponent);
crv.rLimit = tempExp.cmp(MAX_CONVERT_TO_128x128) != -1
? crv.rLimit = type(uint256).max
: crv.rLast.mulDivOrMax(tempExp.to128x128().toUint256(), CAP_PRECISION2);
>> if (cappedReserves[i].mulDiv(CAP_PRECISION, cappedReserves[j]) > crv.rLimit) {
calcReservesAtRatioSwap(mfpWf, crv.rLimit, cappedReserves, i, j, data);
}
} else if (crv.r < crv.rLast) {
crv.rLimit = crv.rLast.mulDiv(
ABDKMathQuad.ONE.div(ABDKMathQuad.ONE.add(crp.maxRateChanges[j][i])).powu(capExponent).to128x128()
.toUint256(),
CAP_PRECISION2
);
>> if (cappedReserves[i].mulDiv(CAP_PRECISION, cappedReserves[j]) < crv.rLimit) {
calcReservesAtRatioSwap(mfpWf, crv.rLimit, cappedReserves, i, j, data);
}
}
}
Though the ConstantProduct2.calcRate
function calculates ratio in the same manner this can be incorrect for other Well Functions since Basin supports Well Functions that contain arbitrary logic
.
Impact
Unintended behavior, potential incorrect calculation for other Well Function implementations.
Tools used
Manual Review
Recommendations
Consider using Well Function for a ratio of cappedReserves
calculation or using crv.r
cached variable if it is appropriate:
CapRatesVariables memory crv;
crv.rLast = mfpWf.calcRate(lastReserves, i, j, data);
crv.r = mfpWf.calcRate(cappedReserves, i, j, data);
// If the ratio increased, check that it didn't increase above the max.
if (crv.r > crv.rLast) {
bytes16 tempExp = ABDKMathQuad.ONE.add(crp.maxRateChanges[i][j]).powu(capExponent);
crv.rLimit = tempExp.cmp(MAX_CONVERT_TO_128x128) != -1
? crv.rLimit = type(uint256).max
: crv.rLast.mulDivOrMax(tempExp.to128x128().toUint256(), CAP_PRECISION2);
- if (cappedReserves[i].mulDiv(CAP_PRECISION, cappedReserves[j]) > crv.rLimit) {
+ if (crv.r > crv.rLimit) {
calcReservesAtRatioSwap(mfpWf, crv.rLimit, cappedReserves, i, j, data);
}
// If the ratio decreased, check that it didn't decrease below the max.
} else if (crv.r < crv.rLast) {
crv.rLimit = crv.rLast.mulDiv(
ABDKMathQuad.ONE.div(ABDKMathQuad.ONE.add(crp.maxRateChanges[j][i])).powu(capExponent).to128x128()
.toUint256(),
CAP_PRECISION2
);
- if (cappedReserves[i].mulDiv(CAP_PRECISION, cappedReserves[j]) < crv.rLimit) {
+ if (crv.r < crv.rLimit) {
calcReservesAtRatioSwap(mfpWf, crv.rLimit, cappedReserves, i, j, data);
}
}