Bounce engaged Guardian to review the security of their Bounce Leveraged Token Protocol. From the 1st of September to the 18th of September, a team of 4 auditors reviewed the source code in scope.
- Published
- Review window
- September 1 to 18, 2025
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Hyperliquid
- Sector
- Derivatives
- 0 Critical
- 3 High
- 9 Medium
- 13 Low
- 10 Informational
Scope
Overview
Bounce engaged Guardian to review the security of their Bounce Leveraged Token Protocol. From the 1st of September to the 18th of September, a team of 4 auditors reviewed the source code in scope.
Findings 35
Main Review
27 findings-
H-01 High Incorrect Spot Index Used Logical Error Resolved
Description
Proof of concept: PoC
The
HyperliquidHandleruses thePrecompileLibto calculate spot and perps values, including thespotAssetValueused by theLeveragedTokento calculate thebaseAssetvalue.However, this function calls
PrecompileLib.normalizedSpotPxwith thetokenId_(token index) instead of the actualspotIndex.Consequently, every call to
spotAssetValuewill revert, causing a major break on theLeveragedTokenfunctionality.This is due to the fact that for the USDT asset the token index is 268 but there are only 219 spot pairs when fetching price.
Recommendation
Calculate the spot index using the
PrecompileLib.getSpotIndexwith the base asset address. Then, use this index in thenormalizedSpotPxcall.Resolution
Bounce Team: The issue was resolved in PR#62.
-
H-02 High Perp Losses Are Absorbed Unexpected Behavior Resolved
Description
Proof of concept: PoC
When users redeem LT for base assets, small redemptions can use the atomic
redeem. Otherwise they use the two-stepprepareRedeem→executeRedeem. InprepareRedeem, the contract precomputes the user’s payout in base units at the current rate and credits it:uint256 baseAmount_ = ltToBaseAmount(ltAmount_); // ... snip ... _burn(msg.sender, ltAmount_); _addCredit(msg.sender, baseAmount_);Later,
executeRedeemtransfers the credited base amount if available. Because the LT’s value depends on perp positions on Core, losses that occur afterprepareRedeemare not applied to users who already prepared: their credited base is fixed.Those losses are effectively shifted to users who redeem or prepare after the drawdown (or to remaining holders), creating unfair loss socialization and potential solvency pressure if many users queue before a loss.
Recommendation
Consider refactoring the redemption flow that snapshots LT and base amounts at
prepareRedeem, then finalizes them atexecuteRedeembased on user inputs (redeemIndex,ltAmount).The payout is capped to the lower of the prep exchange rate and the current exchange rate, ensuring losses are reflected without exceeding the initial snapshot. Global debt is treated as a computed field (
min(debtBaseAmount,ltToBaseAmount(ltToRedeem))) to stay consistent with protocol losses.Resolution
Bounce Team: The issue was resolved in PR#72.
-
H-03 High Step Wise Jump After Bridging Funds Logical Error Resolved
Description
Proof of concept: PoC
When LTs are minted, base assets are deposited into the contract, increasing the
LeveragedToken.totalValueby the token balance scaled to 18 decimals.On the other side, when these assets are bridged out, the
LeveragedToken.totalValuewill rely on the spot value of the bridged USDT0 assets to Core, multiplied by the spot USDT/USDC price.Due to the fact that this spot price is not exactly 1:1, it creates a stepwise jump in the total value and therefore, the exchange rate of the LT.
Even though the spot USDT/USDC price is usually close to $1.00, there are days when volatility kicks in, moving the price +/- 2 %.
However, the
redemptionFeeis set to 0.3%, opening up the possibility of extracting funds by minting and immediately redeeming from the LT. Keep in mind this step wise jump also applies when bridging from Core to EVM.Recommendation
Consider converting USDT (base asset) amounts into USDC, including those held by the EVM contract, preventing step wise jumps in the exchange rate when bridging assets.
The base amount param during minting should also need to be adjusted in terms of the spot price, so all calculations are in USDC terms.
Resolution
Bounce Team: The issue was resolved in PR#71.
-
M-01 Medium Fee Accrual Can Cause Mint And Redeem DoS Resolved
Description
The current implementation of
\_checkpoint()computes the streaming fee owed since the last checkpoint and immediately attempts to pay the full amount by calling\_payStreamingFee(). This requires the contract to have a sufficientbaseAssetBalance().If the owed fee exceeds the available balance, the fee transfer reverts, which in turn causes all user-facing functions that invoke
\_checkpoint()(such asmint(),redeem(), andprepareRedeem()) to revert.As a result, normal user operations may become blocked when liquidity is low, effectively bricking the contract until additional funds are bridged in.
Recommendation
Instead of requiring immediate full payment, track fees as an accrued liability in storage.
Resolution
Bounce Team: The issue was resolved in PR#58.
-
M-02 Medium Final Payout May Be Below User’s Min Amount Unexpected Behavior Acknowledged
Description
Proof of concept: PoC
When a user calls the
prepareRedeemfunction, they specify the minimum amount they wish to receive via theminBaseAmount_parameter.However, the protocol performs its slippage check on the amount before fees are deducted, allowing the transaction to pass even if the final payout is insufficient.
For example, a user might request a minimum of $1,000, but because the redemption fee is calculated later in the
executeRedeemfunction, they may only receive $950. As a result, the user receives less than they were expecting/specified.Recommendation
Consider deducting the fees within the
prepareRedeemfunction so that the user's minimum amount is checked against the final value after fees have been deducted. This also matches the logic done in the redeem function.Resolution
Bounce Team: Acknowledged.
-
M-03 Medium redeployLt Can Redeploy An Unintended Token Unexpected Behavior Resolved
Description
Proof of concept: PoC
If you want to redeploy a leverage token that has already been deployed, you can’t call
createLt. Instead, you must callredeployLt, passing the address of the token you want to redeploy. This function will attempt to swap or move the token, remove the old one, and then call_createLTto deploy a new instance.However, the issue is that
redeployLtdoes not update the_ltIndexmapping. If the owner tries to redeploy a token that’s in the middle of the_ltsarray, it will result in the last two tokens sharing the same index.This can cause unintended behavior: when you try to redeploy another middle token later, the function will incorrectly reference the last element instead of the intended one, causing to redeploy unintended token.
Further Explanation
Imagine we have three tokens: A, B, and C, all deployed. Now, we decide to redeploy B: The code finds its index, which is 1. It moves the last token, C, into B’s spot. The list now looks like [A, C, C]. It then pops the last element, so the list becomes [A, C]. (Notice that
_ltIndexwas not updated.) A new instance of token B (let’s call it newB) is deployed and added to the list, with_ltIndexset to 2.At this point, the list looks like: [A, C, newB]
Next, we try to redeploy token C: The code searches for its index, which is 2. It tries to move the last token (newB) into that spot, but since newB is already there, nothing happens.
It then pops the last element, which is newB, and redeploys a new token. Now the list looks like: [A, C, newC] As you can see, token C was not removed — instead, newB was removed by mistake.
Recommendation
We must update also the
_ltindexof the moved token not just the spot of it in the_ltsarray.Resolution
Bounce Team: The issue was resolved in PR#59.
-
M-04 Medium DoS In LeveragedToken DoS Resolved
Description
The
LeveragedTokencontract relies on theStakercontract for fee distribution via the_payFeesfunction, which callsstaker_.donateFees(baseAmount_). In the Staker contract'sdonateFeesfunction, there is a checkif (div_ = 0) revert ZeroBalance();wherediv_ = totalStaked - totalUnstaking.If there are no effective stakers (i.e.,
totalStaked = totalUnstaking), this revert occurs, causing_payFeesto fail. This revert propagates to keyLeveragedTokenfunctions that call_payFeesdirectly or indirectly:_checkpoint(): Called inmint(),redeem(),prepareRedeem(),executeRedeem(),bridgeOut(), andbridgeIn(). A revert here blocks all these operations._payStreamingFee(): Called in_checkpoint(), causing streaming fee payments to fail._payRedemptionFee(): Called inredeem()andexecuteRedeem(), blocking redemptions. WhentotalStaked = totalUnstaking(e.g., all users have unstaked or are in the process of unstaking), the contract enters a DoS state for core functionalities like minting, redeeming.Recommendation
Add a retry mechanism:
uint256 internal _pendingFees; // Track unpaid fees function _payFees(uint256 baseAmount_) internal { LeveragedTokenStorage storage $ = _getLeveragedTokenStorage(); if (baseAmount_ = 0) return; IStaker staker_ = $.globalStorage.staker(); uint256 amount_ = baseAmount_ + _pendingFees; _baseAsset().safeIncreaseAllowance(address(staker_), amount_); try staker_.donateFees(amount_) { _pendingFees = 0; } catch { _pendingFees = amount_; } }Resolution
Bounce Team: The issue was resolved in PR#60.
-
M-05 Medium Inability To Bridge In Logical Error Resolved
Description
The current
bridgeInflow will calculate thecoreAmount_usingevmToWeihelper function. This converts an EVM amount to a Core (wei) amount.However, the flow later uses
bridgeToEvmwhich expects an EVM amount, not Core. Consequently, the value passed will be converted again.For USDT0, the double conversion will increase the value 100 times (2 EVM extra decimals so multiplied by 10*2).
Recommendation
When using Core amounts, make sure to use the correct
bridgeToEvmsignature, withisEvmAmount= falseas follows:CoreWriterLib.bridgeToEvm(tokenId_, coreAmount_, false);.Alternatively, avoid converting from EVM to core and just pass the
amount_function param:CoreWriterLib.bridgeToEvm(tokenId_, amount_);.Resolution
Bounce Team: The issue was resolved in PR#61.
-
L-01 Low Unfair Lock Duration Best Practices Acknowledged
Description
The
lock()function sets the unlock time asblock.timestamp + lockDurationfor all users, regardless of when they join. However, rewards accrue linearly from_startTime(set by the first locker) to_startTime + lockDuration.Late joiners must wait the full
lockDurationto unlock, but if they join after_startTime + lockDuration, they earn no rewards yet are locked for the entire period. This discourages late participation and may lead to bad user experience.Recommendation
function lock(uint256 amount_) external override { // ... existing checks ... _startDistribution(); _checkpoint(msg.sender); _bounce().safeTransferFrom(msg.sender, address(this), amount_); _locked[msg.sender] = amount_; // Set unlock time to the end of reward distribution for fairness uint256 rewardEndTime = _startTime + lockDuration; if (block.timestamp > rewardEndTime) { // If rewards have ended, allow immediate unlock or reject lock revert RewardsEnded(); } _unlockTime[msg.sender] = rewardEndTime; // Align with reward period end totalLocked = amount_; emit Lock(msg.sender, amount_); }Resolution
Bounce Team: Acknowledged.
-
L-02 Low Claim And Recover Overlap Validation Resolved
Description
Users can claim their airdrop via
Airdrop:claim(amount, merkleProof). Claims are blocked only whennow > deadline:if (block.timestamp > deadline) revert DeadlinePassed();Unclaimed tokens can be recovered by the owner via
recover, which blocks recovery only whennow< deadline:if (block.timestamp < deadline) revert AirdropOngoing();At
now = deadline, both conditions pass:claimstill allows claims (since>is false) andrecoveralso allows recovery (since<is false).This creates a race at the exact deadline and can lead to unexpected reverts or inconsistent outcomes depending on call ordering.
Recommendation
Consider refactoring the claim condition to be inclusive and include the deadline, so that the airdrop ends when now > deadline.
Resolution
Bounce Team: The issue was resolved in PR#55.
-
L-03 Low Checkpoint Order Causes Wrong Reverts Error Resolved
Description
In
executeRedeem, the balance check is performed before_checkpoint():if (baseAssetBalance() < baseAmount_) revert NotEnoughBaseAsset(); _checkpoint();This ordering means the balance validation can use outdated state. If
_checkpoint()reduces the available balance by applying fees, the transfer may later fail with a low-level ERC20 revert (ERC20:transfer amount exceeds balance) instead of the intended custom errorNotEnoughBaseAsset.This issue is also present in
bridgeOut. This leads to misleading error messages. SupposebaseAssetBalance()returns 1,000. A user callsexecuteRedeemwithbaseAmount_ = 1,000. The initial check passes since 1,000 is not less than 1,000.Immediately after,
_checkpoint()runs and deducts fees, reducing the actual available balance below 1,000. The subsequentsafeTransferthen reverts with a genericERC20insufficient balance error, rather than the contract’s intendedNotEnoughBaseAsseterror.Recommendation
Move
_checkpoint()before the balance check so the validation always reflects the current state.Resolution
Bounce Team: The issue was resolved in PR#56.
-
L-04 Low Missing Validation For Perp Owner Update Validation Resolved
Description
The
LeveragedTokenwill be deployed with aperpOwnerwhich is the user holding the leveraged position in Hyperliquid.The current implementation allows global owner to update this address via
setPerpOwnerwithout any validation, creating a stepwise jump inLeveragedToken._hyperliquidValueandHyperliquidHandler.marginUsedRecommendation
Avoid perp owner change if there are active positions.
Resolution
Bounce Team: The issue was resolved in PR#57.
-
L-05 Low Unnecessary Approval Best Practices Acknowledged
Description
In order to pay referral fees, the
LeveragedTokencontract gives theReferrals.solcontract an allowance.However, the contract grants an allowance for the entire
baseAmount_(the redemption fee), even though only a small portion is needed to pay the rebates.Recommendation
Reset the allowance, after calling the
donateRebatesfunction in theLeveragedTokencontract.Resolution
Bounce Team: Acknowledged.
-
L-06 Low Superfluous Max Redemption Check Superfluous Code Resolved
Description
In order to calculate
_redemptionFee, the function will choose the minimum value between the current redemption fee times the LT leverage, with the max redemption fee set in the config file.The max leverage in Hyperliquid is 40x, and the current deployment configuration suggests that redemption fee is 0.3%, while the max redemption fee is 50%.
The
_redemptionFeewill calculate the following:return redemptionFee_.min(baseAmount_.mul($.globalStorage.maxRedemptionFeeShare()));
However, this will never return the max fee calculation, as
12% < 50%, adding unnecessary checks and wasting gas.Recommendation
Remove the max redemption fee check. Alternatively, adjust the configuration values to make sure the fee will be capped at some point.
Resolution
Bounce Team: The issue was resolved in PR#69.
-
L-07 Low Precision Loss In Reward Calculation Rounding Acknowledged
Description
Proof of concept: PoC
In the Locker contract, reward distribution uses integer division. This causes precision loss, leaving small amounts (dust) unclaimed in the contract.
In attached
test_DustLeftover_after_claims, with 2 users (1000e18 and 500e18 locked, 1000e18 rewards), total claimed is 999999999999999999000 (999.999e20), leaving 1000 wei unclaimed.Recommendation
Add a
rescueFundsfunction for owner to recover unclaimed amounts.Resolution
Bounce Team: Acknowledged.
-
L-08 Low Streaming Fee Avoided Due To Rounding Rounding Resolved
Description
When
totalAssetsvalue is low, theperiodFee_calculation can round to 0 if_checkpointis called frequently (low time elapsed).However, the
lastCheckpointis still updated even if_payFeesearly returns due tobaseAmount_being zero.Recommendation
Do not update
lastCheckpointtimestamp if no fees are paid, unlesstotalAssetsare 0.Resolution
Bounce Team: The issue was resolved in PR#70.
-
L-09 Low Potential Rate Volatility Bridging To Core Gaming Acknowledged
Description
According to the Hyperliquid docs, Transfers from
HyperEVMtoHyperCorehappen in the same L1 block as theHyperEVMblock, immediately after theHyperEVMblock is built.However, after some research in docs, code and Hyperliquid discord, there’s no guarantee that the reflection takes place in the next EVM block, not even a clear statement in the docs to support that the next EVM block will have all previous Core queued actions processed.
Consequently, the
LeveragedTokenmay be temporarily land in an invalid state, when tokens are in transit during bridging.Recommendation
Be aware of this issue and document it for users. Will be wise to monitor the exchange rate for any stepwise jumps during normal operation
Resolution
Bounce Team: Acknowledged.
-
L-10 Low No Vesting Token Recovery Mechanism Logical Error Resolved
Description
The Vesting contract has no owner-only recovery function. If the contract is overfunded, ends with dust due to rounding, or the vesting setup changes (e.g., revocations) leaving surplus tokens, these tokens cannot be retrieved and remain stuck forever.
Recommendation
Add an owner-only sweep function to recover arbitrary
ERC20tokens (at minimum the Bounce token) to a specified address. Optionally restrict recovery to when no claimable amount remains or include safety checks.Resolution
Bounce Team: The issue was resolved in PR#73.
-
I-01 Informational Underwater Accounts Can Cause Partial DoS DoS Resolved
Description
The
perpValue()function reverts whenaccountValuereturned fromPrecompileLib.accountMarginSummary()is negative. In practice, this state can occur when a user’s perpetual positions are underwater.As a result,
perpValue()(and consequentlyhyperliquidValue()) becomes unusable for such accounts, preventing integrations from retrieving portfolio values and causing a partial DoS to theLeveragedToken contract.Recommendation
Instead of reverting, consider returning zero.
Resolution
Bounce Team: The issue was resolved in PR#53.
-
I-02 Informational Incorrect Revert Message Error Resolved
Description
The
Staker.claim()validates if claiming is enabled, but throws the revert messageAlreadyEnabled, which is used duringenableClaiming.Recommendation
Consider updating the revert message to clearly show that claiming is not enabled.
Resolution
Bounce Team: The issue was resolved in PR#54.
-
I-03 Informational Users Could Spam Prepare Redeem Events Best Practices Acknowledged
Description
The protocol implements a minimum transaction amount to forbid users from spamming small mints/redeems.
However, if a user is down to deal with the minimum amount, pay the gas fees, and handle that loss; can spam the prepare redeem function, which will send prepare events to the off-chain relayer.
Recommendation
Ensure that the off-chain relayer doesn't trigger bridging calls for every emitted event, but rather batches them together, either depending on a threshold or a time interval.
Resolution
Bounce Team: Acknowledged.
-
I-04 Informational Unused Imports And Errors Best Practices Resolved
Description
- Unused import
import {HyperliquidHandler} from "./HyperliquidHandler.sol";insrc/Factory.sol - Unused import
import {IHyperliquidHandler} from "./interfaces/IHyperliquidHandler.sol";in
src/LeveragedToken.sol- Unused errors in
src/interfaces/ILeveragedToken.sol: error NoCredit();error UnexpectedSize();error NotIsolated();- Unused errors in
src/interfaces/IReferrals.sol: error NotLeveragedToken();
Recommendation
Remove unused imports and errors
Resolution
Bounce Team: The issue was resolved in PR#63.
- Unused import
-
I-05 Informational Min Lock Never Set During Deployment Configuration Resolved
Description
During deployment, the
deployAndConfigureGlobalStoragewill set config variables inGlobalStorage, but it's missing the min lock amount.Therefore, user's will be able to lock 1 wei in
Lockercontract, potentially opening new attack vectors.Recommendation
Be sure to set the min lock amount during deployment:
globalStorage.setMinLockAmount(Config.MIN_LOCK_AMOUNT);Resolution
Bounce Team: The issue was resolved in PR#64.
-
I-06 Informational Missing Validation On Vesting Duration Validation Resolved
Description
The
Vestingconstructor initialized thevestingDuration. However, if the duration is 0, created vest won't be able to claim due to division by 0.Recommendation
Validate
vestingDuration >0duringVestingconstructor.Resolution
Bounce Team: The issue was resolved in PR#65.
-
I-07 Informational Mutable Base Asset Address Configuration Resolved
Description
Contracts fetch the base asset address from an external
GlobalStoragecontract at call time. Due to the fact that this token address is upgradable/mutable by owner, it will cause new calls to operate on a different token, locking user funds and opening DoS vectors.Recommendation
Consider to only set the
baseAssetonce inGlobalStoragejust like the bounce token.Resolution
Bounce Team: The issue was resolved in PR#66.
-
I-08 Informational Some Functions Are Missing Event Emits Best Practices Resolved
Description
Some functions, such as
cancelTransferandsetPerpOwnerare missing event emits despite changing state.Recommendation
Make sure to add an event emit for
cancelTransferandsetPerpOwnerResolution
Bounce Team: The issue was resolved in PR#67.
-
I-09 Informational Immutable Variables Can Be Cached Gas Optimization Resolved
Description
Some contracts constantly read from the
GlobalStoragestate, even though the values will never change.This is the case for
bounceandbaseAsset, which should never change after being set. Although Hyperliquid fees are very low, it's best practice to avoid these calls and optimize gas.Recommendation
Consider caching the
bounceandbaseAssettoken address, using immutable variables if possible.Resolution
Bounce Team: The issue was resolved in PR#68.
Remediation Review
8 findings-
M-01 Medium Wrong Decimal Comparison Validation Resolved
Description
When the owner attempts to update the perps owner, the contract checks that the current owner does not hold any value on Core.
This check is performed by comparing
_hyperliquidAssetsagainst a constant defined in global storage,perpOwnerHoldsFundsThreshold.The issue arises because
_hyperliquidAssetsreturns values with 6 decimals (matching the base asset’s decimals), whileperpOwnerHoldsFundsThresholdis defined with 18 decimals.Specifically, it is set in
src/constants/Config.solas1e18and later applied in the deployment script. As a result, the condition will never revert as the assets will be lower than 1e18:if (_hyperliquidAssets() > $.globalStorage.perpOwnerHoldsFundsThreshold()) revert PerpOwnerHoldsFunds();Recommendation
Ensure that both values use consistent decimal precision. Since
_hyperliquidAssetsreturns values in 6 decimals, update the constant insrc/constants/Config.solto1e6instead of1e18and acknowledge that future changes to the threshold should maintain this decimal consistency.Resolution
Bounce Team: The issue was resolved in PR#74.
-
M-02 Medium Fees For Periods Of Inactivity Paid Unexpected Behavior Resolved
Description
Proof of concept: PoC
In case there are periods of inactivity(most likely right after deployment) with no users, new users can be forced to pay the streaming fees for that period.
Let's assume
lastCheckpointis set by callingcheckpoint()and there are no users. Attacker donates 1 wei of base asset to LT contract which makestotalAssets() > 0and callscheckpoint.Fee calculation for 1 wei will round down to 0. The following will be true and checkpoint calls will return early:
if (periodFee_ = 0 totalAssets() > 0) return;If a new user mints and redeems, they will pay the streaming fee from the
lastCheckpointthat was set at the beginning. This makes them pay fees for the period they did not participate.Recommendation
Consider minting shares for protocol owned address right after deployment so that
totalAssetsandtotalSupplyare > 0.This will also fix the other issue of exchange rate calculation which will happen when a user mints and redeems leaving at least 1 wei behind.
This will make
(totalSupply() > _INITIAL_SUPPLY)andtotalAssets > 0, soexchangeRate()reaches infinity DoSing new mints.Resolution
Bounce Team: The issue was resolved in PR#75.
-
M-03 Medium Checkpoint Function Permissionless Access Control Resolved
Description
The new public
checkpointfunction allows any user to execute it at any given time. As long as the time elapsed and period fee are greater than 0, it will pay streaming fees.Previously, the checkpoint was only meant to be executed during minting and redeeming, which involved a min transaction size.
However, now users can execute it in short intervals, causing rounding errors to reduce the final streaming fees paid.
Additionally, users can execute the checkpoint with an old timestamp, increasing the likelihood of rounding issues, as they just need to pass a value 1 second older than the latest checkpoint.
Recommendation
Consider making the
checkpointfunction permissioned, only callable by the keeper.Resolution
Bounce Team: The issue was resolved in PR#75.
-
M-04 Medium Infinite Exchange Rate Logical Error Resolved
Description
Proof of concept: PoC
When the
LeveragedTokenis launched, depositing base assets will mint LT at a one to one ratio. However, if the user immediately redeems LT balance minus 1 wei, the new exchange rate calculation will dramatically increase.This is due to the fact that total assets is scaled to 18 decimals, and the
divfunction will add another 18 decimals, before dividing by_INITIAL_SUPPLY + 1Recommendation
Consider minting protocol owned liquidity right after deployment so that
totalAssetsandtotalSupplyare not zero.Resolution
Bounce Team: The issue was resolved in PR#77.
-
L-01 Low Streaming Fee Should Be Capped Validation Resolved
Description
The redemption fee is now capped at 2% (0.02e18). However, the streaming fee and rebates still used a max value of 100%.
Recommendation
Consider adding a max value for streaming fee and rebates.
Resolution
Bounce Team: The issue was resolved in PR#76.
-
L-02 Low Direct Redeemers Can DOS executeRedeemers DoS Acknowledged
Description
prepareRedeemis used for the case when the current contract balance is not enough to cover a redemption.The shares of those users are kept in the LT contract until funds arrive via bridge. When funds arrive other user can call
redeemand deplete funds of the contract temporarily DoS'ing the users who have prepared a redeem.This can have more impact during times of market stress when users are rushing to redeem.
Recommendation
Add the following check in redeem:
uint256 currentBalance = _baseAsset().balanceOf(address(this)); uint256 reserved = ltToBaseAmount($.credit); // Convert total prepared LT to base assets if (currentBalance > reserved) { // Funds have arrived: Check if redemption fits in available balance after reserving if (currentBalance - reserved < baseAmount_) revert InsufficientBalance(); } else { // Funds not fully arrived: Skip reserved check, just ensure redemption fits in current balance if (currentBalance < baseAmount_) revert InsufficientBalance(); }Resolution
Bounce Team: Acknowledged.
-
L-03 Low Unsupported Base Assets Logical Error Acknowledged
Description
The
HyperliquidHandlerfunctions accept a base asset param, so values are returned in terms of the asset value and decimals.The spot decimals are hardcoded to 8 and base asset decimals to 6. Although the base asset will initially be USDT0, not all assets have these decimals. For example, USDHL has 6 token decimals, but spot value has 7 decimals, instead of 8.
Recommendation
If the
HyperliquidHandlershould only support USDT0, it will be better to hardcode or set it at deployment time, instead of requiring abaseAssetparam every time.Alternatively, if the idea is to support any
baseAssetand reuse the handler functions, consider fetching the spot decimals instead of using the hardcoded values.Resolution
Bounce Team: Acknowledged.
-
I-01 Informational Hardcoded Config Addresses Configuration Acknowledged
Description
The
MARKET_MAKER,DAO, andTREASURYaddresses are hardcoded to 0x...02/03/04. These are almost certainly not controlled by any known private keys or multisigs.If the system transfers funds to these addresses (e.g., allocations, fees), the assets will be permanently lost with no recovery path.
Recommendation
Do not hardcode placeholder addresses. Parameterize these addresses at deployment and enforce they are nonzero and owned multisigs/contracts.
Resolution
Bounce Team: Acknowledged.
No findings match.
More from Bounce Tech
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.