Guardian's review of AMM for Baseline Markets, published September 2025. The report records 54 findings, including 3 critical and 6 high.
- Published
- Review window
- August 27 to September 13, 2025
- Language
- Solidity
- Chains
- Blast, Base
- Sector
- Token launches
- 3 Critical
- 6 High
- 13 Medium
- 11 Low
- 21 Informational
Scope
15 files in scope · 2,525 nSLOC
| File | nSLOC | Lines |
|---|---|---|
src/ActionRouter.sol | 125 | 184 |
src/BProxy.sol | 94 | 144 |
src/BToken.sol | 15 | 21 |
src/libraries/AllocatorLib.sol | 154 | 267 |
src/libraries/NativeLib.sol | 37 | 51 |
src/libraries/ProxyLib.sol | 26 | 36 |
src/libraries/StateLib.sol | 117 | 182 |
src/libraries/SweepLib.sol | 41 | 68 |
src/components/BAllocator.sol | 83 | 117 |
src/components/BCredit.sol | 386 | 622 |
src/components/BFactory.sol | 126 | 186 |
src/components/BHook.sol | 163 | 214 |
src/components/BLens.sol | 359 | 605 |
src/components/BSwap.sol | 772 | 1371 |
src/utils/ConfigScript.sol | 27 | 33 |
Findings 54
-
C-01 Critical Sells Can Be Used To Drain The Pool Gaming Acknowledged
Description
Because of the nature of the Baseline AMM, where the curve shifts upon every trade and uses a different convexity for the bid side versus the ask side, there is an arbitrage opportunity where a trader sells their BTokens and immediately buys them back at a lower price. This arbitrage can be abused to effectively drain the pool.
The issue stems from the convexity (slippage) difference. If a user buys BTokens from the pool, if they attempt to sell them back to the pool they receive less reserves than they originally bought them with if the bid curve has greater convexity (more slippage). However on the flip side, when the bid curve has greater convexity than the ask curve of the previous snapshot there is an opportunity for arbitrage in the opposite direction by shorting the BToken.
Any BToken holder can sell their BTokens on the higher slippage bid curve and then buy them back using the lower slippage ask curve. Since the ask curve has lower slippage they can buy their original BToken holdings back at a lower average price and arbitrage risk free profits.
The swap fee can counteract the profitability of these trades, however arbitrages still exist at 25-30 basis points of a swap fee and likely even higher.
Recommendation
A lot of issues around arbitrage and overall execution price gaming will stem from the snapshot system and the ability of the AMM curve to change after every swap. If the dynamic AMM curves must be kept, then these arbitrages and gaming vectors may be alleviated by allowing curves to adjust over a period of time rather than instantaneously.
-
C-02 Critical BLV Inflation Attack Gaming Acknowledged
Description
Since the backing value increase is attributed to the remaining circulating supply, in the case where the remaining circulating supply after a swap is extremely small then the BLV price increases significantly in an instant.
This behavior can be taken advantage of to drain the entire reserve value from the protocol at the cost of buying up the entire circulating BToken supply.
First a trader stakes 1 wei of BTokens. Then they buy up the entire circulating supply minus 1 wei which attributes all of the backing value increase to just 1 wei making BLV on the order of 1e38 decimals. Then the trader borrows all of the protocol reserves using their 1 wei of BTokens as collateral, which is valued at a fortune due to the inflated BLV.
This action can be carried out with a flashloan to buy up the entire BToken circulating supply and profit the difference in reserves value that the protocol is holding.
Pool before swap -------------- Pool State -------------- 1000000000000000000000000 totalSupply 800000000000000000000000 circulatingSupply 200000000000000000000000 Pool BTokens 200000000000000000000000 Pool Reserves 1000000000000000000 Pool Active Price 100000000000000000 BLV Price 250000000000000000 Book Price 80000000000000000000000 blv Reserves 120000000000000000000000 buffer Reserves ---------------------------------------- Pool after Swap -------------- Pool State -------------- 1000000000000000000000000 totalSupply 1 circulatingSupply 999999999999999999999999 Pool BTokens 1000000000000000000000 Pool Reserves 1010000000000000000000000000000000000000 Pool Active Price 1000000000000000000000000000000000000000 BLV Price 1000000000000000000000000000000000000000 Book Price 1000000000000000000000 blv Reserves 0 buffer Reserves ----------------------------------------Recommendation
Consider capping the amount that BLV can increase per block or period of time to a modest percent.
-
C-03 Critical Wrong Calculations During Vault Enter/Exit Logical Error Acknowledged
Description
When a pool with vault is deployed and users interact with it the amount of tokens in the vault are:
vaultBalance = totalReserves - totalDebt + unclaimedFees + unrecordedVaultYieldHowever when the admin switches to a new vault or exits the vault this no longer is the case:
- Exit
The exit flow is the following:
- All reserves are redeemed from the vault
- The
totalReservesvariable is updated to:totalReserves = redeemed + totalDebt - unclaimedFees
Here lies the first issue. As we can see in the first formula
totalReservesis increased by theunrecordedVaultYield. Therefore this yield is stolen from the staker and the book price suddenly increases which allows MEV attacks.- Enter
The new vault is entered by depositing the
totalReserves.This is the second issue. As we can see in the first formula the vault balance is not supposed to be
totalReserves.This leads to many issues:
- Potential DoS as it tries to deposit
totalDebtinto the vault (which is not inside the contract) unclaimedFeesare not deposited into the vault and they are left inside the baseline contract. This has multiple negative effects, for example:- It messes with future yield as in the
getHarvestableYieldcalculation theholdingsstay the same but theassetsWithYielddecreased. Therefore future yield is not tracked / stolen from stakers for a while. - As the vault is connected to the pool the
giveReservesfunction will always try to withdraw the given reserves from the vault howerunclaimedFeesare now part of calculations but aren't deposited in the vault. This can therefore lead to DoS.
- It messes with future yield as in the
However the biggest issue lies in the recalculation of
totalReservesbased on theredeemedamount (during exit). If any reserve tokens were not deposited in the vault during that time the resultingtotalReservesis wrong (smaller than before). This happens if tokens lay inside Uniswap and aren't sweeped yet or as already mentioned if a vault switch already happened in the past as theunclaimedFeeslay inside the Baseline contracts in that case.The impact of the decreased
totalReservesvalue is drastical as the book price will be influenced and critical invariants like for example:totalReserves >= circSupply * BLVcan break.And of course malicious actors could trigger it on purpose by performing a swap on Uniswap right before a vault switch.
Recommendation
- Sweep tokens on Uniswap and claim yield before entering or exiting a vault.
- Deposit
totalReserves - totalDebt + unclaimedFeesinstead oftotalReservesinto the new vault.
-
H-01 High Invalid costBasis Tracking Logical Error Acknowledged
Description
The
updatePositionfunction incorrectly tracks thecostBasisof the pool by assuming that the circulating supply has not been updated for the current swap at the time of invocation.In the case where a trader is buying BTokens from the pool and the
bTokenDeltais less than zero, thenewCircSupplyis calculated as thecircSupply + tokenAmountin an attempt to create the new circulating supply of the system after the swap. However the swap has already been recorded in the pool amount and thus is already reflected in thecircSupplyvalue.Instead the costBasis calculation needs to compute the circulating supply from before the buy occurred to get the reserves that were already used in the costBasis.
Recommendation
Replace the circulating supply logic and costBasis calculation in the buy case with the following:
if (bTokenDelta < 0) { uint256 oldCircSupply = circSupply - tokenAmount; mkr.costBasis = (oldCircSupply * mkr.costBasis + tokenAmount * executionPrice) / circSupply; } -
H-02 High Average Price Assignment Allows Arbitrage Gaming Acknowledged
Description
Because the Baseline AMM curve shifts which each swap, there is a superior buying strategy which allows users to obtain BTokens at a lower average price by splitting up larger buys into multiple smaller ones, to take advantage of the way that the curve changes it’s convexity and active price.
For example, consider the following test cases and outputs:
function test_buyTokensFull() public { uint256 bTokensOut = 10_000 * WAD; vm.prank(alice); uint256 actualReservesIn = bSwap.buyTokens(bToken, bTokensOut, WAD * 100_000); console.log("Total reserves in:", actualReservesIn); // 10,531.578947368421060000 } function test_buyTokensSplit() public { uint256 bTokensOut = 5_000 * WAD; vm.startPrank(alice); uint256 actualReservesIn = bSwap.buyTokens(bToken, bTokensOut, WAD * 100_000); actualReservesIn += bSwap.buyTokens(bToken, bTokensOut, WAD * 100_000); vm.stopPrank(); console.log("Total reserves in:", actualReservesIn); // 10,395.310728744939278860 }In the first test case, the total cost to obtain 10,000 BTokens is 10,531.57 reserves, however just splitting this buy up into 2 smaller buy swaps allows the user to obtain 10,000 BTokens for 10,395.31 reserves which is ~1.3% less reserves required. This difference can increase with more chunked buys and larger buys relative to the pool’s liquidity.
This occurs mainly because the price of the AMM is assigned as the average price of the swap that was previously executed, which affects the execution price of the following swap. This behavior means that the pool starts to follow a different path depending on the size, direction, and order of swaps that are executed in the AMM. Unlike a typical CPMM where X number of buy volume and Y number of sell volume will always result in the same price regardless of ordering (assuming full range liquidity), the Baseline AMM will have a different outcome for every permutation of action which is extremely gameable. Malicious actors can simply create orderings with buy volume that yields a lower average entry price than a subsequent ordering of the same magnitude sell volume and yield immediate gains against the AMM.
Recommendation
Consider reconstructing the logic of the AMM such that the price follows one path as a function of the reserves being made available rather than the average execution price of previous swaps.
-
H-03 High Sweep Does Not Work Logical Error Acknowledged
Description
There is an external
sweepfunction on theBHookcontract which allows anyone to sweep the outstanding funds in the Uni V4 hook if there are any.However, this function cannot be used since the pool manager
unlockCallbackfunction attempts to call theexecuteSweepfunction on itself.The
executeSweepfunction requires that the caller is thepoolManagerand therefore this function being called from theunlockCallbackwill fail theonlyPoolManagervalidation.Recommendation
Consider making the
executeSweepexternal function on theBHookcontract callable byaddress(this)only, or usingdelegatecallinstead ofcallin theunlockCallback. -
H-04 High Missing Slippage Validation Validation Acknowledged
Description
In the
BHook._swapfunction there is no slippage validation as thelimitis assigned to the extremes of 0 ortype(uint256).max.As a result users can be frontrun to extract significant value from their trades through the
BHook. Furthermore, even if there is no public mempool on a network that mercury is being used on, a user may simply receive a worse execution than they were expecting purely based on the amount of time it takes their order to be included in a block and the market activity that occurs before that.Recommendation
Consider adding support for a slippage parameter in the form of a maxIn/minOut that can be passed along by the user through the
hookDatain the swap function. -
H-05 High Yield Loss Via Claim Function Not Harvesting Rewards Acknowledged
Description
In the Baseline staking module, calling
claimonly updates the timestamp oflastUpdatedbut does not harvest or distribute accumulated yield. As a result, thependingYieldor accumulator is not updated, effectively discarding yield accrued up to that point. Sinceclaimis publicly callable, a malicious actor can repeatedly call it to force Baseline to lose yield on behalf of all users, leading to systemic losses.Recommendation
Consider modifying the
claimfunction to distribute fees and harvest before executing_sync. -
H-06 High Missing Check In updateBlvPrice Validation Acknowledged
Description
The
updateBlvPricefunction makes sure that the given BLV price is not above the active price. However it does not check if the given BLV exceeds the current book price. This would break critical invariants and can happen even if the admin handles this update carefully as the book price fluctuates.Recommendation
Make sure that the give BLV price is below current book price.
-
M-01 Medium Incorrect Hook Mask In Deployment Logical Error Acknowledged
Description
Deployment script uses
Hooks.ALL_HOOK_MASKwhen deployingBHook, advertising all hooks as implemented toPoolManager. SinceBHookdoes not implementafterInitialize,createPoolreverts and no pool can be deployed. Tests don’t catch this because they configure the hook mask correctly.Recommendation
Use only the specific hook flags actually implemented by BHook instead of
Hooks.ALL_HOOK_MASK. -
M-02 Medium Outstanding Reserves May Remain Stuck DoS Acknowledged
Description
If a swap is attempted through the Uniswap router, Baseline records outstanding reserves, expecting that later opposing flow will allow a sweep. However, there is no guarantee this will occur.
sellTokenscan continue happening through the Uniswap router whilebuyTokensare routed through the Baseline router, keeping the outstanding reserves intact.Even if most orders happen via Uniswap, sweep is not performed at the exact step—it waits until the full opposing amount is present. For example:
- Eve buys 500 BTs over Baseline for 500 USDC
- Eve sells 500 BTs over Uniswap for 500 USDC
- Bob buys 100 BTs over Uniswap for 100 USDC
At this point, 100 USDC remain stuck, as the system waits for the remaining 400 USDC on Uniswap before a sweep can occur. Ideally, the outstanding should have been cleared right after Eve’s sell of 500 BTs. If other actions happen in between, the sweep will not trigger, since it requires the full amount. This would also lead to underutilization of assets, since assets in pool manager cannot be rehypothecated diluting yield.
Recommendation
Consider allowing partial sweeps so that reserves can be cleared incrementally instead of requiring the full amount to match.
-
M-03 Medium Repay DoS Griefing Attack DoS Acknowledged
Description
The
repayflow reverts if a user tries to repay more than the full debt of the given position.This enables a griefing vector:
- Bob tries to repay the full debt of his position
- Eve front runs the call and repays 1 wei of the debt of Bob's position
- Bob's transaction reverts as he tries to repay 1 wei too much
Recommendation
Consider repaying the maximum possible in case a user tries to repay more debt than the position has.
-
M-04 Medium Conflicting Deposit Or Withdrawal Fee Vaults Warning Acknowledged
Description
In many ERC4626 vaults there may exist deposit or withdrawal fees which are levied to disincentivize sandwich attacking yield gains which cause stepwise changes in share price.
These vaults are incompatible with the Baseline system as they open up a vector for a user to continuously borrow and repay to have the vault levy a significant amount of fees on the protocol, causing significant grief to the stakers and even causing the pool liquidity amounts to be unbacked.
Recommendation
Be sure to not use the mercury system with any vaults that have a deposit or withdrawal fee.
-
M-05 Medium Re-org BToken Deployment Risk Acknowledged
Description
In the
BFactorycontract thecreateBTokenfunction uses theCREATEopcode to deploy a new BToken which can then be used to deploy a pool. Since the normalCREATEopcode is used for deployment, the resulting BToken address is not unique to the deployer and is dependent on the ordering of deployments made by the factory due to the monotonically increasing account nonce.As a result, in the event of a Re-org it is possible for a malicious actor to siphon funds from a deployer if they had sent a combination of certain transactions in a short period which were perturbed by the Re-org.
Recommendation
Use
CREATE2with themsg.senderas a salt in thecreateBTokenfunction. -
M-06 Medium Mismatching Fees For Leverage And Borrow Logical Error Acknowledged
Description
The fee calculation used in the borrow function is different than the fee calculation used in the leverage function, which allows users to borrow at a lower fee using the leverage function.
In the borrow function, the fee calculation is
amount / (1 - fee) - amount.In the leverage function, the fee calculation is
amount * fee.For example:
- amount = 100
- fee = 10%
- Borrow fee = 100 / 0.9 - 100 = 11.111
- Leverage fee = 100 * 0.1 = 10
Recommendation
Consider standardizing on one fee calculation or the other for both leverage and borrow functions.
-
M-07 Medium Missing Routes Logical Error Acknowledged
Description
The following function routes have not been configured in each Component:
BCredit.previewDepositAndBorrowBLens.poolIdToBTokenBLens.hasHookBLens.poolKeyBStaking.withdrawAndClaimRecommendation
Add the necessary routes for these functions in their respective components.
-
M-08 Medium All BTokens Cannot Be Sold Logical Error Acknowledged
Description
In the
_generateValidPricefunction theexpectedBookPriceis computed by dividing the new pool reserves by the new BToken circulating supply. However if a user is selling the last circulating BToken to the pool this results in a divide by zero revert, therefore preventing the user from selling the final amount of BTokens.Furthermore, in the
_updateBlvPricefunction when the final circulating BTokens are being sold, the calculation of the new BLV price panic reverts with a divide by zero revert.Additionally, In the
ensureValidCurvefunction, if the resulting supply in the pool is equal to the total supply, the calculation of the book price will cause a revert.Finally, the
getBookPremiumRatiofunction will revert when the_bookPrice - _baselineValueresult is 0. This can occur when the not absolutely all of the circulating supply has been sold, but the entire circulating supply less a handful of wei is being sold. In these circumstances where very nearly the entire circulating supply has been sold, the BLV price converges to be the same as the book price and causes a revert when computing the B value. Not only does this prevent sells that leave a handful of wei in the circulating supply by way of reverting in theensureValidCurvefunction. But it also prevents the remaining wei from being sold because of the use of thegetBookPremiumRatiofunction in computing the AMM calculations for that sell.Recommendation
Consider early returning with the original price in the
_generateValidPricefunction if there will be no more circulating supply as a result of the BToken sell and because there is no valid book price in this case.Consider early returning in the
_updateBlvPricefunction when thecirculatingSupplyis 0.Consider only performing the
bookPricevalidations in theensureValidCurveif the supply in the pool is not the same as the total supply.Consider returning a B value of 1 from the
getBookPremiumRatiofunction when the book price is the same as the baseline value price. -
M-09 Medium XSS Attack With Unsanitized Token Details Validation Acknowledged
Description
In the
createBTokenfunction there is no validation on the_nameand_symbolstrings which are used for theBToken. If this BToken is then displayed in the Baseline UI or other consumers of the Baseline pools UI’s, then these name and symbol fields could be leveraged for an off-chain XSS attack against users of those applications, or the applications themselves.This was the case in the EtherDelta exploit, for more details on the EtherDelta exploit refer to this article.
Recommendation
Consider validating that the length of the name and symbol is below a reasonable limit to prevent malicious XSS injection.
-
M-10 Medium Inconsistent Fee Charged Logical Error Acknowledged
Description
In the
computeTokenBuyandcomputeReserveBuyfunctions the fee charged is different because of a different calculation method used. As a result, users will receive a lower net fee using the exactIn reserve buy functionality compared to the exactOut BToken buy functionality.For example, consider the following scenario:
Fee = 10% = 1e17 Assume price is s.t. 1 BToken == 1 Reserve and there is nearly infinite liquidity relative to trade size so ignore slippage
Buying BTokens With ExactIn Reserves
normalizedReservesIn = 100 normalizedFee = 100 * 1e17 / 1e18 = 10 = 10%
Buying BTokens With ExactOut BTokens
reservesInLessFee = 100 normalizedReservesIn = 100 * 1e18 / 9e17 = 111.111111111 = 11.11%
In this case the fee was 11.111… reserves which does not match the fee rate charged in the other buy function.
Recommendation
Consider standardizing on one or the other approach for computing the fee so there is no difference.
-
M-11 Medium sqrtPriceLimit Ignored Warning Acknowledged
Description
In the
beforeSwapfunction aSwapParams_paramsparameter is passed along from the poolManager which includes asqrtPriceLimitX96value that the user has specified. The user expects that the swap will not pass this limit on execution, however this limit is not honored or referenced at all by the Baseline AMM.This may cause users to unexpectedly receive much worse executions then they believed possible based on the
sqrtPriceLimitX96assignment.Recommendation
Consider incorporating the
sqrtPriceLimitX96in theBSwaplogic, or perform this check at the end of the_swapfunction inBSwap.Such a check would not be straightforward to implement the same way that
sqrtPriceLimitX96is implemented in Uniswap though, as the end pool price in BSwap is actually the average execution of the trade rather than the infinitesimal last marginal unit execution as it is in Uniswap.If the average price assignment behavior remains, the meaning of the
sqrtPriceLimitX96value could be merely adapted. -
M-12 Medium Missing Sweep In Borrow Flow DoS Acknowledged
Description
The
borrowfunction does not callsweepto get fetch reserves from thePoolManager. However they could be needed to lend them out to the user and in that edge case the function call would DoS.Recommendation
Consider calling
sweepduring theborrowflow. -
M-13 Medium Yield Distributed To Stakers If There Aren't Any Rewards Acknowledged
Description
The
distributeFeesfunction distributes fees to stakers even if there aren't any stakers in the pool yet.However the yield is not lost and instead claimed by future stakers.
Recommendation
Consider distributing the remaining yield to the protocol and/or creator if no tokens are staked.
-
L-01 Low poolManager Set Twice Gas Optimization Acknowledged
Description
The
poolManagervariable is set twice in theConfigScript. This is redundant.Recommendation
Set it only once to save gas.
-
L-02 Low Fee Claiming May Be Blocked By Zero Amounts DoS Acknowledged
Description
In some cases the
protocolFeeorcreatorFeemay be assigned to zero and it may also be the case that the reserve token for the pool reverts upon zero value transfers. This prevents the claiming of the other non-zero fee value in theclaimPoolFeesfunction as thegiveReservesfunction does not early return when the provided value to transfer is zero.Recommendation
Consider early returning in the
handleOutgoingas well ashandleIncomingfunctions if the_amountvalue is zero. -
L-03 Low Protocol Fee Claim Can Be DoS DoS Acknowledged
Description
The
claimPoolFeesclaims both the fees for the creator of the pool and the protocol itself. In case the transfer to the creator fails because the address reverts or is blacklisted the whole transaction reverts. Therefore in that case it is not possible for the Baseline protocol to claim their earned revenue.Recommendation
Consider adding another function to claim protocol fees or using try/catch.
-
L-04 Low Claimers Miss Yield Harvest Unexpected Behavior Acknowledged
Description
In the claim function in the
BStakingcomponent there is no invocation to harvest the latest yield from the vault with the pool.harvest function. As a result, the latest yield earned by the vault that would go towards the_pool.pendingYieldis not updated.As a result, the
targetTpsand thus thetokensPerSecond_in the accumulator calculation for the_synccall during the claim does not account for this latest accumulated yield. The user is then not credited with the latest accumulator at the time of their claim and therefore is not able to claim the full amount that they should have been.Recommendation
Consider invoicing the pool.harvest function before calling
_syncin theclaimfunction. -
L-05 Low takeReserves Does Not Refund Accidental Eth Validation Acknowledged
Description
In the
takeReservesfunction, the underlyinghandleIncominginternal function refunds excess ether that may have been accidentally sent in the case when the reserve token is the native token.However when the reserve token is not the native token but the
msg.valueis still non-zero, this non-zero value is not refunded to the user.Recommendation
Consider refunding any accidental
msg.valuesent in the event where the reserve token is not the native address. -
L-06 Low Inessential ensureValidCurve Convexity Validation Validation Acknowledged
Description
In the
ensureValidCurvefunction the_convexityFactoruint value is validated to be greater than or equal to 0, however this being a uint value it cannot fail this validation.Recommendation
Consider if the validation should instead validate that the
convexityFactoris not equal to 0. Otherwise remove the validation as it will never fail by definition. -
L-07 Low Misleading Staking Percent Logical Error Acknowledged
Description
The
stakingPercentfunction reports the staking percentage share of fees as1 - creatorFeePct, however the staking percentage is actually represented by:(1 - protocolFeePct) * (1 - creatorFeePct).Recommendation
Replace the
stakingPercentimplementation with(1 - protocolFeePct) * (1 - creatorFeePct). -
L-08 Low Missing Deployment Validation Functions Warning Acknowledged
Description
The BaseHook contract used by Uniswap gives several functions to manage the deployment and validation of a Hook address such as
getHookPermissionsandvalidateHookAddresswhich are used in the constructor to validate that the hook has been deployed to the correct address given it’s permissions.However the
BHookcontract does not have these functions and lacks the proper deployment address validation in the constructor.Recommendation
Consider using the BaseHook contract from Uniswap to improve the overall security of the
BHookcontract and include these important deployment validations. -
L-09 Low Fee Claiming May Be Blocked By Protocol DoS Acknowledged
Description
In the
claimPoolFeesfunction theprotocolFeeRecipientaddress always receives theprotocolFeesamount using_asNativeas true. Therefore if theprotocolFeeRecipientaddress is maliciously or accidentally assigned as an address that cannot accept Ether then the claiming of fees is prevented for all pools where the reserve token is wrapped native.Recommendation
Consider sending the fees as the wrapped native token by passing
_asNativeas false when invokinggiveReservesfor theprotocolFeeRecipient. Otherwise be sure that the protocolFeeRecipient is not assigned to an address that cannot accept ether. -
L-10 Low Frequent Claims Slow Staking Distribution Unexpected Behavior Acknowledged
Description
In the
getAccumulatorfunction during the_syncflow, the tokens per second is affected by the current pendingYield of the protocol. ThependingYieldis decreased over time as stakers interact with theBStakingcontract and the_syncfunction is invoked which moves the realizednewYieldfrom thependingYieldto theclaimableYield.As a result, the time it takes to distribute a certain amount of
pendingYieldis actually affected by the amount of syncs that are done, since thependingYieldwill be reduced with each sync and thetargetTpswill be dragged down over time, redoing thetokensPerSecond.This may lead to the entire yield amount taking an unexpectedly longer time to fully distribute than anticipated.
Recommendation
Consider if this behavior is acceptable and if the number of syncs should have an impact on the amount of time it takes to distribute the entirety of pending yield to stakers. If this behavior is not desired, consider refactoring the staking logic such that the rate of distribution is constant.
-
L-11 Low beforeModifyLiquidity Revert Missing Best Practices Acknowledged
Description
The
BHookprevents most actions on Uniswap side but does not have abeforeModifyLiquidity. Therefore this action is not prevented.Recommendation
Consider implementing a
beforeModifyLiquidityhook which reverts. -
I-01 Informational Misleading Comment Logical Error Acknowledged
Description
In the
createPoolfunction the vault is set for the pool with thesetVaultForBTokenat the very end of the initialization. However the comment on the takeReserves action mentions that it willDeposit to vault if set.This is slightly misleading as the vault is only set at the end of the function and therefore the vault will not ever be deposited into in this
takeReservescall.Recommendation
Consider removing the
Deposit to vault if setpart of the comment above thetakeReservesfunction. -
I-02 Informational Redundant Executor Assignment Acknowledged
Description
In the
executeActionsfunction if the caller is the admin theisExecutorvalidation is skipped, however in theconstructorandacceptAdminfunctions theexecutorsmapping is written to for the admin.This executors entry is not necessary, at least for the immediate use-case of the
executeActionsfunction in theBProxycontract, since the executors validation is always bypassed for the admin.Recommendation
Consider removing the assignment to the
executorsmapping for the admin in theconstructorandacceptAdminfunction. -
I-03 Informational Unused getCollateralForBorrow Function Superfluous Code Acknowledged
Description
In the
BCreditcomponent, thegetCollateralForBorrowfunction is unused throughout the codebase.Recommendation
Consider removing the
BCreditcomponent. -
I-04 Informational Admin Can Be Added As An Executor Validation Acknowledged
Description
In the
addExecutorfunction there is no validation that the admin address cannot be the provided_executor. As a result, the admin can accidentally configure themselves as the_executorand give themselves temporaryexecutorsstatus, overwriting their permanentexecutorsstatus.Recommendation
Consider adding validation that prevents the current admin from being the
_executoraddress. -
I-05 Informational Version Limits Upgrades Warning Acknowledged
Description
In the
_upgradeComponentfunction theuint8newVersionvariable is required to be greater than the previous version. However since the version variables are limited touint8types, a maximum of 255 upgrades can occur for the component.In some cases this may limit upgrades after a long duration of protocol support.
Recommendation
Be aware of this limitation and consider using a larger storage type if more than 255 upgrades for a single component should be supported.
-
I-06 Informational Lacking executeActions Return Value Acknowledged
Description
In the
executeActionsfunction the return data from thedelegatecalloperation is not surfaced as a return value from theexecuteActionsfunction.Recommendation
Consider if this is expected and if it would be useful to surface the return data if there is any from the
delegatecalloperation as a return value from theexecuteActionsfunction. -
I-07 Informational Incorrect Comment Documentation Acknowledged
Description
In the
getSupplyFromReservesAskfunction the comment at the beginning explains thaton ask curve, targetReserves must be >= currentReserves, however the target reserves must instead be strictly>the currentReserves and this is what is implemented by the validation.Recommendation
Consider updating the comment at the beginning of the
getSupplyFromReservesAskfunction to readon ask curve, targetReserves must be > currentReserves. -
I-08 Informational Component Contracts Must Report Static Labels Warning Acknowledged
Description
The
ActionRoutercontract depends on component contracts reporting their own labels via theLABELview function.However if a malicious or malfunctioning component contract reports a
LABELvalue that differs from the value it had reported in the past this leads to an invalidation of the logic in theActionRouter.Recommendation
Be sure to only interact with and support component contracts that return a constant
LABELvalue which cannot change. -
I-09 Informational Admin Can Be Removed As An Executor Validation Acknowledged
Description
The
removeExecutorfunction does not prevent the caller from accidentally or purposefully removing the admin as an executor. This action cannot be reversed without re-transferring admin ownership since theaddExecutorfunction can only be used to add temporary access, not unlimited access.Recommendation
Consider validating that the
_executoraddress is not an admin in theremoveExecutorfunction. -
I-10 Informational Maximum ContractName Label Length Unexpected Behavior Acknowledged
Description
In the Component contract the
toLabelfunction truncates the provided type name to just 32 bytes. Therefore if a contract name exceeds 32 characters then it cannot be fully expressed in the label and may have collisions with other contract names that also exceed 32 characters.Recommendation
Be aware of this limitations and keep the component contract names to below 32 character names.
-
I-11 Informational Lacking Zero Address Checks Validation Acknowledged
Description
In the
constructorandtransferAdminfunction of theBProxycontract there are lacking 0 address checks. For important address values such as the_newAdmin,_admin, and_actionRouter.Recommendation
Consider adding zero address validations in the
constructorandtransferAdminfunction of theBProxycontract. -
I-12 Informational Unnecessary Return Value Superfluous Code Acknowledged
Description
The
depositToVaultandwithdrawFromVaultfunctions state that they will return auint256value in their interface declaration, however only return 0 in the event that the provided_reserveAmountis 0.Furthermore, the return value from this function is not used in the Mercury codebase, therefore it can be removed.
Recommendation
Consider removing the return value from the
depositToVaultandwithdrawFromVaultfunctions. -
I-13 Informational Misleading shareBalance Comment Documentation Acknowledged
Description
In the
Poolstruct it is mentioned that theshareBalanceis the“Amount of shares if allocated. Equal or greater than totalReserves”, however this is not true in several interpretations.Firstly, if this is to mean that the actual shares amount is greater than the amount of reserves in the pool, this is not true in the case where the share price is higher than 1 reserve and the pool reserves are converted to a number of shares that is less than the amount of reserves deployed.
Secondly, if this is to mean that the amount of shares is always worth more than the totalReserves this may also not be the case in the event that the underlying vault is lossy.
Recommendation
Consider removing the the portion of this comment which states that the amount of shares is
“Equal or greater than totalReserves”as this may be misleading and does not always hold. -
I-14 Informational Risk Of Insolvency And Bank Run Warning Acknowledged
Description
Baseline rehypothecates the underlying reserve assets by depositing them into vaults.
The entire logic for deposits, withdrawals, and yield distribution assumes that the
convertToAssetsvalue is monotonically increasing (as is the case with the sUSDS vault).If this assumption is broken, it creates an insolvency scenario: the total reserves of the pool could become lower than what is implied by
shareBalance * currentShareValue.When lossy vaults are considered, these assumptions no longer hold.
Recommendation
Be aware of this risk: underlying vaults integrated with Baseline should always be monotonically increasing in value, similar to the sUSDS vault.
-
I-15 Informational Rounding Risk Warning Acknowledged
Description
Baseline tracks
shareBalancefor rehypothecated vault assets. On deposits, the balance is incremented, and on withdrawals, it is decremented:_pool.shareBalance += newShares; _pool.shareBalance -= newShares;However, vaults round down on deposit and up on withdrawal. Because of this, the two operations may not perfectly cancel out due to precision loss.
This discrepancy would only materialize and cause revert for last user if the vault were fully emptied, which is an unlikely scenario.
Recommendation
Beware of this sceanario.
-
I-16 Informational Accumulator Update Skips Warning Acknowledged
Description
The accumulator is only updated once per block. If no time has elapsed, the function returns early:
uint256 timeElapsed = block.timestamp - staking.lastUpdated; if (timeElapsed == 0) return (accumulator_, 0, tokensPerSecond_);This works as intended for vaults like sUSDS, where yield accrual is block-time based with a constant rate.
However, in dynamic vaults (e.g., rebalancing vaults or lending vaults accruing liquidation penalties), yield can accrue at arbitrary times within a block. Since Baseline skips accumulator updates when multiple actions occur in the same block, an attacker can exploit this by:
- Calling
claimfor any user before yield accrues in the vault (forcing a sync). - Staking afterwards, still within the same block.
- Waiting for subsequent blocks to unstake.
This effectively sandwiches the yield generated by the vault during that block, misattributing it to the attacker.
Baseline’s
timeToAdaptmechanism significantly reduces this risk, since it forces gradual adaptation:uint256 adjustment = FixedPointMathLib.min( (delta * timeElapsed) / meta.timeToAdapt, delta );Still, in cases where yield between periods a → b and b → c differs, attackers can profit if yield from a → b is higher, since they only bear yield distribution for b → c.
Recommendation
Beware of this, when rehypothecating into vaults with dyanmic yield accrual. Thing is there is no solution to this problem besides what baseline already doing, even if sync is enforced on each action, attacker can just sandwich with stake - vault action - unstake.
- Calling
-
I-17 Informational Redundant Check Superfluous Code Acknowledged
Description
The
ensureValidCurvechecks that auint256is not < 0. This can never happen anyway therefore this check is redundant.Recommendation
Adjust or remove the redundant check.
-
I-18 Informational Vault Loss Will Break Calcs Warning Acknowledged
Description
Major issues could arise in case the protocol decides to use a vault with a index which is not strictly increasing (for example as bad debt is socialized among vault participants).
The main reason for that is the
harvestflow which will realize and safe yield from the vault as fixed profit. However the same is not done for loss in the vault. Therefore if profit is already tracked by Baseline but loss in the vault occurs afterwards the system will continue to calculate with tokens it doesn't own.Recommendation
Be aware to not use such vaults.
-
I-19 Informational Lending Is Capital Inefficient Rewards Acknowledged
Description
The Baseline Credit system lends out reserves for a fixed fee, the origination fee which currently is set to 0.1%.
The system also allows to supply all the reserves to Aave or a similar protocol. In this case lending out reserves is probably capital inefficient for the protocol as the yield from Aave will outperform the 0.1% flat fee in a short period of time.
Recommendation
Consider to rethink this part of the system or increase the lending fee.
-
I-20 Informational Variable Missing In NatSpec Best Practices Acknowledged
Description
The
_pricevariable is missing in therecordSwapNatSpec.Recommendation
Consider making sure that all NatSpecs are complete.
-
I-21 Informational Unused Code Superfluous Code Acknowledged
Description
There are unused variables in multiple parts of the code like for example:
- The
metavariable in theharvestYieldfunction - The
stakingvariable in theleveragefunction - The
poolvariable in the_repayandupdateBlvPricefunction
Recommendation
Consider remove unused code.
- The
No findings match.
More from Baseline Markets
All 12 reports-
Mercury, Round 3
109 findings4 critical · 9 high 109 findings: 4 critical, 9 high, 28 medium, 33 low, 35 informational -
AMM, Round 2
47 findings4 critical · 14 high 47 findings: 4 critical, 14 high, 8 medium, 13 low, 8 informational -
Fixed Supply
34 findings4 high 34 findings: 4 high, 10 medium, 20 low -
bToken
8 findings 8 findings: 4 medium, 4 low
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.
