Guardian's review of MIMSwap Updates for Abracadabra Money, published January 2025. The report records 12 findings, including 5 critical and 1 high.
- Published
- Review window
- January 20 to 24, 2025
- Language
- Solidity
- Chains
- Blast
- Sector
- DEXs and AMMs
- 5 Critical
- 1 High
- 3 Medium
- 3 Low
- 0 Informational
Scope
2 files in scope · 555 nSLOC
| File | nSLOC | Lines |
|---|---|---|
src/mimswap/MagicLP.sol | 500 | 671 |
src/oracles/aggregators/MagicLpAggregator.sol | 55 | 84 |
Findings 12
-
C-01 Critical LP Prices Can Be Easily Manipulated After The First buyShares Call Logical Error Pending
Description
In the DODO-like PMM logic of
MagicLP, the contract maintains a state variable_RState_(ONE,ABOVE_ONE, orBELOW_ONE) to reflect whether the pool’s actual ratio of base vs. quote is near, above, or below the “target ratio”_I_. However, when seeding liquidity viabuyShares, the function does not recalculate_RState_using the PMM formulas. Instead, if it’s the initial deposit (i.e.,totalSupply == 0), it leaves_RState_set toONEby default. And for subsequent calls, it leaves_RState_unchanged. This leads to:- A mismatch if the deposited ratio is nowhere near the target ratio
_I_. The pool’s actual ratio may be well above or below_I_, yet_RState_remains set toONE. - Incorrect
MagicLpAggregatorcalculations: The aggregator seesBASE_TARGET/QUOTE_TARGETthat were set under the assumption_RState_ = ONEandbuySharesratio matched_I_. As a result, it returns a wrong LP price until a trade (e.g.,sellBaseorsellQuote) forces the PMM logic to recalculate_RState_properly.
Example scenario:
- The pool is intended for a 1:1 ratio (
_I_() = 1) but the user deposits 4.537 base tokens and 12.524 quote tokens, an actual ratio > 1:2. buySharesseestotalSupply == 0(first deposit) and sets_RState_set toONE, ignoring the fact that the real ratio is ~2.76:1.- The aggregator (which references
_BASE_TARGET_,_QUOTE_TARGET_from a “ONE” perspective) might report an LP price ~2.0, whereas a naive sum-of-reserves would yield ~3.7. - Once a small trade (
sellBase) occurs, the PMM code calculates the real ratio and transitions_RState_toBELOW_ONEorABOVE_ONE, causing a big jump in the aggregator’s reported LP price.
Recommendation
Consider recalculating**
_RState_(and_BASE_TARGET_,_QUOTE_TARGET_) in the **buySharesandsellSharesfunctions. - A mismatch if the deposited ratio is nowhere near the target ratio
-
C-02 Critical Flawed ABOVE_ONE Case Logical Error Pending
Description
The current pricing mechanism can lead to an R_STATE being calculated that does not provide an accurate representation for the R_STATE of the pool and leads to unexpected pricing results and reverts. Consider the following: (1) RState.ABOVE_ONE indicates a shortage of base relative to base target, and a surplus of quote relative to quote target. (2)
((quoteTargetNormalized * r) / i) - ((Q * r) / i)within thelatestAnswerfunction is bound to be negative sinceQ > quoteTargetNormalizedwhen RState.ABOVE_ONE (3) With a smallbaseTargetNormalized, the calculation forBwill ultimately underflow and DoS the LP pricing mechanism entirely. By mixing and match the current pool targets and mapping them to reserves with external prices, this revert is bound to happen.Recommendation
Ensure that in the ABOVE_ONE case the
baseTargetNormalizedexceeds the difference between((quoteTargetNormalized * r) / i) - ((Q * r) / i). -
C-03 Critical LP Prices Can Be Easily Manipulated After The First buyShares Call Logical Error Pending
Description
In the DODO-like PMM logic of
MagicLP, the contract maintains a state variable_RState_(ONE,ABOVE_ONE, orBELOW_ONE) to reflect whether the pool’s actual ratio of base vs. quote is near, above, or below the “target ratio”_I_. However, when seeding liquidity viabuyShares, the function does not recalculate_RState_using the PMM formulas. Instead, if it’s the initial deposit (i.e.,totalSupply == 0), it leaves_RState_set toONEby default. And for subsequent calls, it leaves_RState_unchanged. This leads to:- A mismatch if the deposited ratio is nowhere near the target ratio
_I_. The pool’s actual ratio may be well above or below_I_, yet_RState_remains set toONE. - Incorrect
MagicLpAggregatorcalculations: The aggregator seesBASE_TARGET/QUOTE_TARGETthat were set under the assumption_RState_ = ONEandbuySharesratio matched_I_. As a result, it returns a wrong LP price until a trade (e.g.,sellBaseorsellQuote) forces the PMM logic to recalculate_RState_properly.
Example scenario:
- The pool is intended for a 1:1 ratio (
_I_() = 1) but the user deposits 4.537 base tokens and 12.524 quote tokens, an actual ratio > 1:2. buySharesseestotalSupply == 0(first deposit) and sets_RState_set toONE, ignoring the fact that the real ratio is ~2.76:1.- The aggregator (which references
_BASE_TARGET_,_QUOTE_TARGET_from a “ONE” perspective) might report an LP price ~2.0, whereas a naive sum-of-reserves would yield ~3.7. - Once a small trade (
sellBase) occurs, the PMM code calculates the real ratio and transitions_RState_toBELOW_ONEorABOVE_ONE, causing a big jump in the aggregator’s reported LP price.
Recommendation
Consider recalculating**
_RState_(and_BASE_TARGET_,_QUOTE_TARGET_) in the **buySharesandsellSharesfunctions. - A mismatch if the deposited ratio is nowhere near the target ratio
-
C-04 Critical Stable Pool Pricing Used For Volatile Assets Logical Error Pending
Description
This update was meant to support volatile assets in the LP pair, but the current pool does not use an external oracle to dictate the equilibrium price
_I_. Without protocol owner pausing and intervening, over time the_I_will be become stale, and lead to inaccurate regression targets and pricing within the pool for users. For example, consider the case where token X is initially priced at $1, the_I_guiding price is set to $1, but then the token performs a 2x and becomes $2. The price will be lower than market price, as the_I_value is stale (P = iR), and tokens can be extracted at a cheaper cost.Recommendation
Consider enabling the usage of an external oracle to dictate the
_I_value when calculating the quadratic solutions, as that is the most up-to-date value for the asset. -
C-05 Critical Incorrect Pricing Formulas Used Logical Error Pending
Description
In the
RState.BELOW_ONEcase within theMagicLPAggregator, the reserve formula for theRState.ABOVE_ONEcase is used. Vice versa, for theRState.ABOVE_ONEcase within theMagicLPAggregator, the reserve formula for theRState.BELOW_ONEcase is used. This will lead to inaccurate reserve calculations and inaccurate collateral pricing in the Cauldron.Recommendation
Use the correct formulas. See https://docs.dodoex.io/en/product/pmm-algorithm/details-about-pmm for the formulas necessary. The simplest solution is to just edit the inequality to use the right cases:
quoteTargetNormalized.divWad(baseTargetNormalized) > baseAnswerNormalized.divWad(quoteAnswerNormalized) -
H-01 High k=0 Case Not Handled Logical Error Pending
Description
When
k = 0within the MagicLP, the price of the pair stays fixed at the initial guide pricei, which is a valid pricing curvature for the pair. Within the MagicLPAggregator, the solution forBin the BELOW_ONE case would useikpq.divWad(ikpq + baseAnswerNormalized - ipq, but ifk = 0then it would ultimately turn into0 / (0 + baseAnswerNormalized - ipq)IfbaseAnswerNormalized == ipq, then there will be a division by zero, the aggregator will revert, and consequently users may not be able to lend in the Cauldron or receive their collateral depending on whenkwas modified.Recommendation
Consider enforcing that
k != 0within the MagicLP to entirely avoid this scenario. -
M-01 Medium Lack of Staleness Checks for Oracle Data Validation Pending
Description
The
MagicLpAggregatorcontract callslatestAnswerdirectly for bothbaseOracleandquoteOraclewithout verifying whether these oracles have recent and valid price data. Moreover, according to Chainlink’s documentation, thelatestAnswerfunction is deprecated.latestAnswerdoesn't throw an error when there is no answer but returns0.Recommendation
It is recommended to ensure the prices retrieved from the price feeds are not too old (e.g., older than X seconds). Also, it is recommended to enforce that the price is higher than 0 to ensure that it is a valid price. To get proper access to all these data, the
latestRoundDatafunction should be used instead of the deprecatedlatestAnswer. Example code snippet:(, int256 price, , uint256 updatedAt, ) = baseOracle.latestRoundData(); require(block.timestamp - updatedAt <= MAX_STALE_PERIOD, "Stale oracle price"); require(price > 0, "Invalid price"); -
M-02 Medium Lack of Staleness Checks for Oracle Data Validation Pending
Description
The
MagicLpAggregatorcontract callslatestAnswerdirectly for bothbaseOracleandquoteOraclewithout verifying whether these oracles have recent and valid price data. Moreover, according to Chainlink’s documentation, thelatestAnswerfunction is deprecated.latestAnswerdoesn't throw an error when there is no answer but returns0.Recommendation
It is recommended to ensure the prices retrieved from the price feeds are not too old (e.g., older than X seconds). Also, it is recommended to enforce that the price is higher than 0 to ensure that it is a valid price. To get proper access to all these data, the
latestRoundDatafunction should be used instead of the deprecatedlatestAnswer. Example code snippet:(, int256 price, , uint256 updatedAt, ) = baseOracle.latestRoundData(); require(block.timestamp - updatedAt <= MAX_STALE_PERIOD, "Stale oracle price"); require(price > 0, "Invalid price"); -
M-03 Medium Risk Of Overflow On Sqrt Calculation Overflow Pending
Description
Within the usage of more volatile assets, there is now a greater risk of overflow when solving the quadratic functions. For example, function
_SolveQuadraticFunctionForTargetrisks overflow within the else-if case:else if ((ki * delta) / ki == delta)Depending on the K and I values (which can be up to 10^36 and 10^18) respectively, the multiplication with a large delta will overflow, which effects theadjustedTargetfunction and hence most protocol actions. Another example isuint256 squareRoot = DecimalMath.mulFloor((DecimalMath.ONE - k) * 4, DecimalMath.mulFloor(k, V0) * V0);within_SolveQuadraticFunctionForTradewhich can overflow when multiplyingK * V0 * V0which ultimately leads to failed trades.Recommendation
Consider using libraries that support intermediate overflows. Furthermore, clearly document the risks with using large K values, and tokens with large decimals.
-
L-01 Low Inaccurate Event Emission Events Pending
Description
Function
setTargetsemitsTargetChanged(baseTarget, quoteTarget)but the pool's new base and quote targets can be different than the parameters passed in due to the_adjustTarget()call, which may shift the targets depending on whether the pool is long base or quote.Recommendation
Clearly document this behavior or adjust the event to use the actual
_BASE_TARGET_and_QUOTE_TARGET_post-adjustment. -
L-02 Low setParameters Stepwise Jump in Lp Price Validation Pending
Description
When the
MagicLPowner or an authorized operator, uses thesetParametersfunction to update the pool parameters and then almost immediately unpauses or reactivates the pool, there will be an abrupt jump in the aggregator’s reported LP price as the aggregator’s math depends on those key parameters. ThesetParameterscall is expected to be executed frequently in protocol owned pools. This call or, which is the same, this jump in the aggregator’s reported LP price, could be easily sandwiched for a profit.Recommendation
Be aware of this constrain whenever the
MagicLpAggregatoris integrated and used by any other protocol. -
L-03 Low correctRState LP Price Manipulation Warning Pending
Description
When a user takes a flashloan, it opens a precarious situation for the protocol since the reserves leave the LP temporarily, and if the targets were to be adjusted this would manipulate the price of the LP collateral. An attack path such as
flashloan -> syncis prevented because functionsynchas the nonReentrant modifier, butcorrectRStatedoes not. AlthoughcorrectRStateis usually not triggered as it requires a rare protocol state, it is best to prevent reentrancy targets entirely given how adjustments in the targets will influence the valuation of collateral within the Cauldron.Recommendation
Add a
nonReentrantmodifier to functioncorrectRState. Furthermore, document that callingcorrectRStatedirectly will affect the valuation of collateral.
No findings match.
More from Abracadabra Money
All 19 reports-
MagicGLP Upgrade
11 findings1 high 11 findings: 1 high, 10 low -
PurrSwap Aerodrome
105 findings9 high 105 findings: 9 high, 23 medium, 45 low, 28 informational -
PurrSwap ve33
74 findings5 critical · 10 high 74 findings: 5 critical, 10 high, 20 medium, 39 low -
Hyperliquid OFT Deployment
3 findings 3 findings: 1 low, 2 informational
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
