Platform: Code4rena
Start Date: 07/03/2024
Pot Size: $63,000 USDC
Total HM: 20
Participants: 36
Period: 5 days
Judge: cccz
Total Solo HM: 11
Id: 349
League: BLAST
Rank: 10/36
Findings: 3
Award: $2,014.53
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SpicyMeatball
Also found by: Breeje
1790.3744 USDC - $1,790.37
A malicious actor can manipulate LP pricing, similar to the exploit witnessed in the Warp Finance Hack
.
The latestAnswer
function in MagicLpAggregator
is used to calculate the current price of the LP token for a given pool.
function latestAnswer() public view override returns (int256) { uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals())); uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals())); uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized; (uint256 baseReserve, uint256 quoteReserve) = _getReserves(); baseReserve = baseReserve * (10 ** (WAD - baseDecimals)); quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals)); @-> return int256(minAnswer * (baseReserve + quoteReserve) / pair.totalSupply()); }
Calculation Process:
minAnswer * (baseReserve + quoteReserve) / pair.totalSupply()
Here, The issue lies in the 3rd point: return value from the _getReserves()
function, which is susceptible to manipulation. This function retrieves the spot reserves of the pool. So When a malicious user dumps large amount of any token into the pool, baseReserve + quoteReserve
will be significantly increased. Thus, this value should not be utilized for pricing LP tokens due to its volatility caused by trades executed within the pool.
Warp Finance
used the formula: r0 * p0 + r1 * p1
to calculate their TVL similar to how MagicLpAggregator
contract is using: min(p0, p1). r0 + min(p0, p1). r1
, a method that has proven susceptible to exploitation.
Given the ability to alter spot reserves, this vulnerability enables anyone to artificially inflate the price within a single block without incurring any risk, utilizing flashloans. Thus it is very important to mitigate this risk.
Manual Analysis
Use different approach to price the LP in better way as shown here
Context
#0 - 0xm3rlin
2024-03-15T00:26:38Z
Disputed
#1 - c4-pre-sort
2024-03-15T12:42:02Z
141345 marked the issue as duplicate of #75
#2 - c4-judge
2024-03-29T16:53:59Z
thereksfour marked the issue as satisfactory
#3 - c4-judge
2024-04-05T15:39:34Z
thereksfour marked the issue as partial-75
🌟 Selected for report: grearlake
Also found by: Breeje, blutorque, hals, roguereggiant
208.8293 USDC - $208.83
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L470
An attacker can steal some funds from the LPs when owner updates parameter like _I_
.
_I_
is basically the market price provided by an oracle. So updating the value of _I_
directly influences the price.
function setParameters( address assetTo, uint256 newLpFeeRate, uint256 newI, uint256 newK, uint256 baseOutAmount, uint256 quoteOutAmount, uint256 minBaseReserve, uint256 minQuoteReserve ) public nonReentrant onlyImplementationOwner { if (_BASE_RESERVE_ < minBaseReserve || _QUOTE_RESERVE_ < minQuoteReserve) { revert ErrReserveAmountNotEnough(); } if (newI == 0 || newI > MAX_I) { revert ErrInvalidI(); } if (newK > MAX_K) { revert ErrInvalidK(); } if (newLpFeeRate < MIN_LP_FEE_RATE || newLpFeeRate > MAX_LP_FEE_RATE) { revert ErrInvalidLPFeeRate(); } _LP_FEE_RATE_ = uint64(newLpFeeRate); _K_ = uint64(newK); _I_ = uint128(newI); _transferBaseOut(assetTo, baseOutAmount); _transferQuoteOut(assetTo, quoteOutAmount); (uint256 newBaseBalance, uint256 newQuoteBalance) = _resetTargetAndReserve(); emit ParametersChanged(newLpFeeRate, newI, newK, newBaseBalance, newQuoteBalance); }
Now consider the following scenario:
Q
and Base B
with a initial ideal price I_Old
._K_
negligible for easier calculation, Price of B
wrt Q
now equals I * Q
.I_Old
to I_New
value where I_New
> I_Old
.In case, the owner's transaction is to decrease _I_
, Alice can do the opposite and still end up getting the profit at the expense of Liquidity providers.
VS Code
Implement a Mechanism such that calls to sellBaseToken
and sellQuoteToken
should revert whenever pool parameters are changed in that block.
MEV
#0 - 0xm3rlin
2024-03-15T00:43:48Z
no factor
#1 - c4-pre-sort
2024-03-15T12:31:31Z
141345 marked the issue as duplicate of #171
#2 - c4-judge
2024-03-29T16:58:30Z
thereksfour marked the issue as satisfactory
🌟 Selected for report: ether_sky
Also found by: 0x11singh99, 0xE1, 0xJaeger, Bauchibred, Bigsam, Bozho, Breeje, DarkTower, HChang26, SpicyMeatball, Trust, ZanyBonzy, albahaca, bareli, blutorque, grearlake, hals, hassan-truscova, hihen, oualidpro, pfapostol, ravikiranweb3, slvDev, zhaojie
15.328 USDC - $15.33
There are multiple areas where Multiplication is performed on the result of a division in Math.sol contract.
One such example is in _SolveQuadraticFunctionForTrade
function:
uint256 part2 = (((k * V0) / V1) * V0) + (i * delta); // kQ0^2/Q1-i*deltaB
As denoted by the accompanying comment, the intention is to multiply k
by the square of V0
. However, the current implementation deviates from this intention by initially multiplying k
with V0
, then dividing the result by V1
, followed by another multiplication by V0
.
In scenarios where the value of k = 1
and V0 < V1
, the initial expression (k * V0) / V1)
could be rounded down to zero due to integer division truncation. Consequently, the entire initial expression kQ0^2/Q1
might be rounded down to zero, resulting in a loss of value due to precision.
This discrepancy is noteworthy since k = 1
is a legitimate value that creators might opt to employ.
MagicLpAggregator
won't support >18 decimal tokensThere are token with more than 18 decimals precision and MagicLpAggregator
can't handle it as WAD - decimals
expression will always revert.
function latestAnswer() public view override returns (int256) { // SNIP (uint256 baseReserve, uint256 quoteReserve) = _getReserves(); @-> baseReserve = baseReserve * (10 ** (WAD - baseDecimals)); @-> quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals)); // SNIP }
#0 - c4-pre-sort
2024-03-15T15:00:48Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-16T01:08:42Z
212 Breeje l r nc 0 0 1
L 1 n L 2 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/88
#2 - c4-judge
2024-03-29T16:55:17Z
thereksfour marked the issue as grade-b