Open code423n4 opened 1 year ago
mattt21 marked the issue as sponsor confirmed
This is a remarkable finding. However, I'll downgrade it to medium as assets are not strictly speaking directly at risk (they can't be stolen and the state of the system cannot be manipulated to grieve another user).
We could also argue that this is a case of "function incorrect as to spec" which is according to C4 doc of low severity.
Considering the importance of the finding, and especially the fact that it could lead to users receiving fewer rewards than they expected, I think Medium severity is appropriate.
Picodes changed the severity to 2 (Med Risk)
Picodes marked the issue as satisfactory
Lines of code
https://github.com/code-423n4/2023-03-mute/blob/4d8b13add2907b17ac14627cfa04e0c3cc9a2bed/contracts/amplifier/MuteAmplifier.sol#L473-L499 https://github.com/code-423n4/2023-03-mute/blob/4d8b13add2907b17ac14627cfa04e0c3cc9a2bed/contracts/amplifier/MuteAmplifier.sol#L366-L388 https://github.com/code-423n4/2023-03-mute/blob/4d8b13add2907b17ac14627cfa04e0c3cc9a2bed/contracts/amplifier/MuteAmplifier.sol#L417-L460
Vulnerability details
Impact
This report deals with how the calculation of the
multiplier
in theMuteAmplifier
contract is not only different from how it is displayed in the documentation on the website but it is also different in a very important way.The calculation on the website shows a linear relationship between the
dMUTE / poolSize
ratio and theAPY
. ThedMUTE / poolSize
ratio is also called thetokenRatio
.By "linear" I mean that when a user increases his
tokenRatio
from0
to0.1
this has the same effect as when increasing it from0.9
to1
.The implementation in the
MuteAmplifier.calculateMultiplier
function does not have this linear relationship betweentokenRatio
andAPY
.An increase in the
tokenRatio
from0
to0.1
is worth much less than an increase from0.9
to1
.As we will see this means that all stakers that do not have a
tokenRatio
of exactly equal0
or exactly equal1
lose out on rewards that they should receive according to the documentation.I estimate this to be of "High" severity because the issue affects nearly all stakers and results in a partial loss of rewards.
Proof of Concept
Let's first look at the
multiplier
calculation from the documentation:The example calculation shows that the amount that is added to $APY{base}$ (5%) is scaled linearly by the $\dfrac{user{dmute}}{pool_{rewards}}$ ratio which I called
tokenRatio
above.This means that when a user increases his
tokenRatio
from say0
to0.1
he gets the same additional reward as when he increases thetokenRatio
from say0.9
to1
.Let's now look at how the reward and thereby the
multiplier
is calculated in the code.The first step is to calculate the
multiplier
which happens in theMuteAmplifier.calculateMultiplier
function:Link
The
multiplier
that is returned is then used to calculate thereward
:Link
Let's write the formula in a more readable form:
$\dfrac{lpTokenOut weightDifference}{stakeDivisor - tokenRatio (stakeDivisor - 1)}$
$stakeDivisor$ can be any number $>=1$ and has the purpose of determining the percentage of rewards a user with $tokenRatio=0$ gets.
For the sake of this argument we can assume that all numbers except $tokenRatio$ are constant.
Let's just say $stakeDivisor=2$ which means that a user with $tokenRatio=0§ would receive $\dfrac{1}{2}=50\%$ of the maximum rewards.
Further let's say that $lpTokenOut * weightDifference = 1$, so 100% of the possible rewards would be $1$.
We can then write the formula like this:
$\dfrac{1}{2 - tokenRatio}$
So let's compare the calculation from the documentation with the calculation from the code by looking at a plot:
x-axis: tokenRatio
y-axis: percentage of maximum rewards
We can see that the green curve is non-linear and below the blue curve.
So the rewards as calculated in the code are too low.
Tools Used
VSCode
Recommended Mitigation Steps
I recommend to change the reward calculation to this:
$(lpTokenOut weightDifference) (percentage{min} + clamp(\dfrac{user{dmute}}{pool{rewards}},1) * (1 - percentage{min}))$
Instead of setting the
stakeDivisor
upon initialization, thepercentageMin
should be set which can be in the interval[0,1e18]
.Fix: