Valantis engaged Guardian to review the security of their Overseer.Sol. From the 5th of January to the 6th of January, a team of 2 auditors reviewed the source code in scope.
- Published
- Review window
- January 5 to 6, 2026
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Hyperliquid
- Sector
- DEXs and AMMs, Staking
- 0 Critical
- 1 High
- 2 Medium
- 4 Low
- 17 Informational
Scope
Overview
Valantis engaged Guardian to review the security of their Overseer.Sol. From the 5th of January to the 6th of January, a team of 2 auditors reviewed the source code in scope.
Findings 24
Main Review
17 findings-
H-01 High Minting Allowed After Slashing Logical Error Resolved
Description
Proof of concept: PoC
The
mintfunction call sendsmsg.valuebefore checkingpendingSlashExists. If backing is below expected (slash pending), a minter can “plug” the shortfall with their deposit, makingpendingSlashExistsreturn false and letting mint proceed without a rebase.Because
stHYPEmints 1:1, the minter effectively bails out the deficit and then gets their new tokens slashed later, shifting the loss from existing holders to the minter and bypassing the intended “no mint/redeem until slash is applied” policy.Recommendation
Decouple pending-slash detection from the current call’s inbound funds (
msg.value).Resolution
Valantis Team: The issue was resolved in commit 6eeacef.
-
M-01 Medium Interim Balance Excluded From Backing Unexpected Behavior Acknowledged
Description
The new accounting for total backing no longer includes the deprecated
interimAddress.getTotalBalanceonly sums theOverseerbalance and staking module balances, so any HYPE that remains parked atinterimAddressis invisible to the accounting.That invisible balance makes the protocol believe that backing is lower than expected, which triggers the slashing path in
_accountForSlashingand also causespendingSlashExiststo return true. That combination can either over slash holders by reducing supply as if a loss occurred, or block mint and redeem flows until a rebase is forced.On a forked mainnet upgrade test the
interimAddressbalance was non zero. The EVM balance was 10999506489256520459 wei (about 10.999506489 HYPE) and the L1 balance was 29130000000000 wei (about 0.00002913 HYPE), for a total of 10999535619256520459 wei (about 10.999535619 HYPE).With a total supply around 4 million HYPE, that discrepancy is well above the
minSlashPercentagethreshold, so it would be interpreted as a real slash even though those funds still exist. The impact is incorrect slashing or a protocol wide pause caused by a false pending slash.Recommendation
Drain
interimAddressbefore the upgrade and verify it is zero, or keepinterimAddressin total backing calculations until it is drained. If you want a guardrail, add a pre upgrade check that reverts or warns when theinterimAddressbalance is non zero.Resolution
Valantis Team: Acknowledged.
-
L-01 Low Legacy Burns Can Revert _redeemable DoS Resolved
Description
The V3 upgrade introduces a per burn mapping called
cumulativeSlashFactorthat is used inside_redeemableto compute the current redeemable amount. This mapping is only populated whenburn()runs in the new logic, so any burns created before the V3 upgrade havecumulativeSlashFactorequal to zero.The
_redeemablefunction performs the division bycumulativeSlashFactorbefore checking whether the burn is completed, so a legacy burn ID causes a division by zero revert even if it was already completed. The V3 initializer does not backfillcumulativeSlashFactorfor historical burns or for the new dummy burn, so this condition persists for old entries. The core issue is the order of operations in_redeemable.The upgrade also has a no pending burns guard, but it only runs if
initializeV3is actually called:require(burns[burns.length - 1].sum == redeemed, CannotUpgradeWhilePendingBurns());This check prevents pending burns at upgrade time, but it does not initialize
cumulativeSlashFactorfor legacy burns, so the division by zero still occurs for completed historical entries. If the upgrade were executed without callinginitializeV3, then even the pending burn check would not run.As a result,
redeemable(burnId)andgetBurns(account)revert for accounts with historical burns andredeem(legacyBurnId)reverts during its pre check. In the expected upgrade path whereinitializeV3is called, there should be no pending burns, so funds are not stuck.The persistent impact is a view and integration level DoS for users with legacy burns and any offchain systems that call these view functions. The worst case is a user level lockout from redemption only if an upgrade is executed without calling
initializeV3or if the pending burn guard is bypassed, because a pending legacy burn would be unrecoverable while the division by zero remains.Recommendation
Make
_redeemablesafe for legacy entries by returning early whenburns[burnId].completedistrueand by guarding zero slash factors. One safe pattern is to read the factor into a local variable and returnfalseif it is zero, or treat zero as E18 for legacy burns:if (burns[burnId].completed) return false; uint256 burnFactor = $.cumulativeSlashFactor[burnId]; if (burnFactor == 0) return false;Resolution
Valantis Team: The issue was resolved in commit 09b6375.
-
L-02 Low Insolvency Can Revert Backing Math DoS Acknowledged
Description
Several critical paths subtract
protocolPendingFeeortotalLiabilitydirectly fromgetTotalBalance. In normal operation this is safe, but after a catastrophic loss it is possible for total backing to fall belowprotocolPendingFeeortotalLiability.In that case these subtractions revert, which can block
rebase,mintandredeemflows at the moment when the protocol most needs a controlled recovery. This is not an unchecked underflow, but a liveness edge case in an insolvency scenario. It primarily affects incident response and recovery workflows rather than creating a new theft path.uint256 totalStHypeBacking = getTotalBalance() - protocolPendingFee; return getTotalBalance() - totalLiability();The impact is that an extreme backing loss can cause core state transitions to revert until governance intervenes.
Recommendation
Consider saturating these subtractions to zero or adding explicit checks that allow a controlled emergency mode instead of reverting. If insolvency is possible, add a governance write down path for
protocolPendingFeeand document an incident response procedure that can restore liveness.Resolution
Valantis Team: Acknowledged.
-
L-03 Low Asymmetric Slashing State Calculation Logical Error Resolved
Description
Proof of concept: PoC
The
pendingSlashExiststreatsslashPercentage >= minSlashPercentageas pending, but_accountForSlashingonly updateslatestCumulativeSlashFactorwhenslashPercentage >minSlashPercentage.At equality, the slash is never applied yet the pending flag never clears, leaving mint/redeem permanently blocked (liveness DoS).
Recommendation
Align the comparator between detection and application (use the same inequality or explicitly clear pending at/under the threshold) and add a boundary test for the equality case.
Resolution
Valantis Team: The issue was resolved in commit ff7e607.
-
I-01 Informational ERC7201 Slot Change Upgrade Risk Upgradeability Acknowledged
Description
The storage slot anchor for the
StakingModuleExternalManagementmodule was changed to a newERC7201namespace constant. A proxy that was initialized with the old slot keeps its state at the old location, but the new implementation reads from the new location.If an already deployed proxy is upgraded in place from the old implementation to the new one, rather than being freshly deployed with the new slot, the proxy will appear uninitialized, with manager and stake account reading as zero and deposit cap as zero.
The namespace change is:
- old namespace:
stHYPE.storage.StakingModule - new namespace:
stHYPE.storage.StakingModuleExternalManagement
All
onlyManagerfunctions will revert, deposits will fail the cap check and any funds already held by the module or its stake account become operationally stuck. In addition, total balance reporting will ignore the real stake account, which can make the protocol think backing has disappeared and may trigger slashing logic or block rebases.This only happens if an already deployed
StakingModuleExternalManagementproxy is upgraded in place. The current upgrade script does not upgrade those proxies, and modules 1 through 5 are a different module type, so this does not trigger in the provided flow.Recommendation
Treat this as an upgrade risk. Do not upgrade existing
StakingModuleExternalManagementproxies that were initialized with the old slot to the new implementation. Keep the original slot constant for in place upgrades, or only use the new slot for fresh deployments.Resolution
Valantis Team: Acknowledged.
- old namespace:
-
I-02 Informational Max Slash Unit Migration Risk Upgradeability Acknowledged
Description
The upgrade renames
slashThresholdBpstomaxSlashPercentageand changes its unit from basis points to 1e18 scaled percentage, but no migration or conversion is performed. The upgrade scripts pass empty calldata toupgradeAndCalland the V3 initializer does not updatemaxSlashPercentage, so any existing non zero value is preserved in storage and interpreted with the new unit.A prior value like 500 (5 percent in basis points) becomes 500 in 1e18 scale, which is effectively near zero. When a real slashing event occurs,
_accountForSlashingcompares the 1e18 scaled slash percentage against this tiny threshold and reverts, causing rebase to fail.While a pending slash exists, mint and redeem are blocked and
maxRedeemablereturns zero, so the protocol cannot process exits and supply cannot be updated. On the current fork we observed a pre upgrade value of 0, so the mis scaling does not manifest there; this remains a migration footgun if the value is ever non zero on upgrade.Recommendation
Migrate the value during upgrade by converting the old basis points value to 1e18 scale, or explicitly reset
maxSlashPercentageto the intended 1e18 scaled threshold immediately after upgrade viaOverseer.setMaxSlashPercentage(uint256)(DEFAULT_ADMIN_ROLE).The safest approach is to include a migration step in the
upgradeAndCallpayload or ininitializeV3, and to assert the post upgrade value is correct.Resolution
Valantis Team: Acknowledged.
-
I-03 Informational Extreme Slash Can Freeze Rebase Flow DoS Acknowledged
Description
If the protocol suffers an extreme loss where total backing is essentially zero relative to the expected backing, the slashing factor will round down to zero.
In that case, the computed slash percentage becomes 100%, which exceeds the configured max slash threshold.
The slashing logic reverts and rebase cannot complete. The pending-slash check still returns true, so minting and redeeming are blocked while the system cannot progress via rebase.
This is an edge case, but it creates a freeze where the protocol is stuck until governance intervenes.
Recommendation
Merely informative. Consider documenting this in an emergency playbook.
Resolution
Valantis Team: Acknowledged.
-
I-04 Informational Non-atomic Upgrade Leaves V3 Uninitialized Upgradeability Acknowledged
Description
The V3 implementation relies on
initializeV3to set newERC7201storage values, includinglatestCumulativeSlashFactor. If the proxy is upgraded to the new implementation without callinginitializeV3in the same transaction, key paths can revert because the new slot values remain zero.For example, burn updates
cumulativeNormalizedBurnsusinglatestCumulativeSlashFactorand_redeemableperforms divisions that rely on the new per burn factors. With zeroed storage, these divisions revert and burn or redeemable views break untilinitializeV3is executed.This is primarily an operational risk and appears mitigated by the concrete upgrade flow tested in
test/integration/V3Upgrade.t.sol, which usesupgradeAndCallwithinitializeV3. The issue only manifests if an operator performs a non atomic upgrade or simply skipsinitializeV3.Recommendation
Enforce an atomic upgrade path that always calls
initializeV3viaupgradeAndCalland document this requirement in the upgrade runbook.Resolution
Valantis Team: Acknowledged.
-
I-05 Informational transferFrom Permits Burns To address(0) Logical Error Resolved
Description
Proof of concept: PoC
The token restricts burns to the
BURNER_ROLEand allows pausing burns, buttransferFromdoes not block a zero-address recipient. As a result, any spender with an allowance, including a holder who self-approves, can calltransferFromwith the recipient set to the zero address.This routes to the internal transfer routine, treats the zero address as a burn, and decreases
preSyncSupplyand total voting units. This bypasses the burn role and burn pause controls and allows unauthorized burns through the allowance path.Recommendation
Add the same zero-address recipient check used by
transfertotransferFrom, or explicitly gate zero-address burns behind the burner role and burn pause by routing burns through a dedicated burn-only function.Resolution
Valantis Team: The issue was resolved in commit 43081af.
-
I-06 Informational Burns Can Revert During Active Rebase Logical Error Acknowledged
Description
During an active sync interval, total supply is the sum of a base amount (
preSyncSupply) plus a linearly accruing rewards component.The burn path in
_transferalways subtracts the full burn amount frompreSyncSupply. If a user attempts to burn more thanpreSyncSupplywhile rewards are still accruing, the subtraction underflows and reverts.This means large burns can fail mid interval even though the user balance includes accrued rewards. Because
Overseerburns route throughstHYPE.burn, a large user exit can be blocked until the interval completes andpreSyncSupplyis reset by rebase.The implementation acknowledges this assumption by noting that rewards are not high enough to have to worry about
preSyncSupplyunderflowing, so the issue is mainly a worst case edge scenario.if (to == address(0)) { preSyncSupply -= SafeCast.toUint96(amount); }Recommendation
If you want burns to be robust during active intervals, cap the
preSyncSupplyreduction at its current value and reduce the remaining amount from the rewards component or adjustrewardsToSyncaccordingly.Resolution
Valantis Team: Acknowledged.
-
I-07 Informational selfDisableTransfer Bypassed By Allowances Unexpected Behavior Resolved
Description
The
selfDisableTransferfeature only checksmsg.sender, not the token owner whose balance is being moved. As a result, a user who setsselfDisableTransferto true can still have tokens moved by any spender that was previously approved.The spender passes
notSelfDisableTransferbecause it checks the spender address, thentransferFromproceeds and moves funds out of the disabled account.This defeats the intuitive expectation that
selfDisableTransferfreezes outgoing transfers from the account, at least with respect to already granted allowances. The impact is limited to accounts that have approved spenders, but it can still surprise users who enableselfDisableTransferfor self protection.Recommendation
In
transferFrom, also enforce thatselfDisableTransfer[from]isfalsewhen moving tokens out of an account. If you want stronger semantics, block approvals or clear allowances when a user self disables.Resolution
Valantis Team: The issue was resolved in commit fc8a082.
-
I-08 Informational Historical Balance Returns Shares Unexpected Behavior Acknowledged
Description
The historical balance and supply view functions (
balanceOf(address,uint256)andtotalSupplyAt(uint256)) return values fromVotesUpgradeable, which are stored as shares (voting units), not 18 decimal token balances.balanceOf(account, timepoint)returnsgetPastVotesandtotalSupplyAt(timepoint)returnsgetPastTotalSupply. Both values are in share units, so any consumer that assumes token units will read mis scaled values.Converting to token balances would require the historical
totalSupplyRawor balance per share at each timepoint, which is not tracked. The impact is limited to offchain consumers and governance analytics that rely on these views without realizing the unit mismatch.function balanceOf(address account, uint256 timepoint) external view returns (uint256) { return getPastVotes(account, timepoint); } function totalSupplyAt(uint256 timepoint) external view returns (uint256) { return getPastTotalSupply(timepoint); }Recommendation
Document explicitly that these functions return shares, or rename them to make the unit clear. If historical 18 decimal balances are required, snapshot
totalSupplyRawor balance per share at timepoints and convert shares to balances accordingly.Resolution
Valantis Team: Acknowledged.
-
I-09 Informational Burn To Invalid Recipient Loses Funds Unexpected Behavior Resolved
Description
The
burnfunction does not validate the redemption recipient. A user can passaddress(0)as the recipient. Onredeem, the ETH is force sent to that address, so sending toaddress(0)irreversibly burns the ETH.This is not a permission bypass, but it allows a user to permanently lose their redemption proceeds by mistake.
Recommendation
Validate that the recipient is not the zero address.
Resolution
Valantis Team: The issue was resolved in commit 487b7b5.
-
I-10 Informational Redeemable Rounding Can Be Early Rounding Resolved
Description
The
redeemablecheck computes liabilities up to aburnIdby multiplyingcumulativeNormalizedBurnsby the latest slash factor with integer division.Because
cumulativeNormalizedBurnsalready stores per burn values rounded down, the extra floor can make the computed sum slightly lower than the true sum of per burn redeemables.In some scenarios, this can allow a later burn to be deemed redeemable before it actually is by a few wei. The effect is limited to dust level rounding.
uint256 sum = ($.cumulativeNormalizedBurns[burnId] * $.latestCumulativeSlashFactor) / E18; uint256 redeemedHype = ($.normalizedRedeemedHype * $.latestCumulativeSlashFactor) / E18; uint256 difference = sum < redeemedHype ? 0 : sum - redeemedHype;Recommendation
Compute the difference in normalized units and apply a rounding up conversion to be conservative when checking available balance.
For example, use
Math.mulDivwithRounding.Ceil. If you want strict burn ordering, track anextRedeemableBurnIdpointer and require burns to be redeemed in order.Resolution
Valantis Team: The issue was resolved in commit 94f9f0e.
-
I-11 Informational Unused Error Superfluous Code Resolved
Description
The
BelowMinimumBurnAmountis still declared but never used in the code.Recommendation
Remove unused error.
Resolution
Valantis Team: The issue was resolved in commit 32e771b.
-
I-12 Informational Frontrunning V3 Upgrade Configuration Acknowledged
Description
The current V3 upgrade fork test shows the implementation of the actions before/after upgrade. However, there is no pausing enforced, which allows any user to trigger a burn just before the upgrade, forcing the admin to clear the queue before continuing with the upgrade.
Recommendation
Consider enforcing pausing mechanisms before the upgrade.
Resolution
Valantis Team: Acknowledged.
Remediation Review
7 findings-
M-01 Medium Min Slash Threshold Skews Losses Unexpected Behavior Acknowledged
Description
The slashing logic only updates the cumulative slash factor when the computed slash percentage meets or exceeds the configured minimum threshold. At the same time, the rebase logic still derives new supply directly from actual backing, so a small backing loss below the threshold can still decrease supply.
Because the cumulative slash factor is not updated in this case, pending burns are not reduced proportionally to that loss, which shifts the dust loss onto current holders rather than distributing it across pending burns. The maximum slash percentage guard is also only applied when the threshold is met, so this path bypasses that guard for small losses.
if (slashPercentage >= minSlashPercentage) { latestCumulativeSlashFactor = latestCumulativeSlashFactor * slashingFactor / 1e18; }This can lead to a fairness drift where holders absorb tiny losses that pending burns do not, and it weakens the expectation that a zero max slash setting prevents any supply decrease.
Recommendation
Align the threshold behavior with supply updates. If the intent is to ignore dust, then avoid reducing supply for losses below the threshold or explicitly treat the dust as a protocol loss while keeping pending burns consistent. If the intent is that
maxSlashPercentageequal to zero prevents any decrease, add an explicit guard to stop supply reductions when that setting is in effect.Resolution
Valantis Team: Acknowledged.
-
L-01 Low Redemption Pause Also Blocks Mint Unexpected Behavior Resolved
Description
The
mintpath calls the pending slash check, and that check returns true whenever redemptions are paused. As a result, callingpauseRedemptionalso blocks minting even though there is a separate burn pause mechanism.This coupling can be intentional for incident response, but it is not obvious from the pause function names and can surprise operators who expect only redemptions to stop.
if (isRedemptionPaused()) return true; ... if (_pendingSlashExists()) revert CannotMintWhilePendingSlash();The behavior means a redemption pause is effectively a global pause for minting, which should be treated as a policy choice rather than an accident.
Recommendation
Document this coupling explicitly. If the intent is to pause redemption while still allowing mint, split the gating conditions so mint checks a dedicated mint pause flag and redemption checks a redemption pause flag.
Resolution
Valantis Team: Resolved.
-
I-01 Informational Ops Scripts Mismatch Contract Interface Best Practices Acknowledged
Description
Multiple operational scripts reference contract functions that are not present in the current Overseer and
stHYPEinterfaces, so running them will revert or target a non existent selector.In
ops/rebase.sh, the script invokescalculateAprwith auint256argument andrebasewith auint256argument, but the contract only exposescalculateApr()andrebase()with no parameters.In
ops/withdraw.sh, the script callswithdrawToL1Escrow, which is not part of theOverseercontract.In
mockrebase.sh, the script callstotalAssetSupplyonstHYPEandreceiveFromL1onOverseer, neither of which exist, and also uses rebase(uint256)which does not exist.These scripts can mislead operators into thinking actions were executed successfully when in fact the transactions revert.
Recommendation
Update the scripts to match the deployed ABI and function signatures, or remove or clearly deprecate them to prevent accidental execution.
Treat these scripts as production artifacts by pinning them to the exact deployed commit and ABI, and add a lightweight preflight check that validates selectors against the target contract before sending transactions.
Resolution
Valantis Team: Acknowledged.
-
I-02 Informational RescueTokens Uses Raw Transfer Best Practices Resolved
Description
The
rescueTokensfunctions useIERC20(token).transfer(to, amount)without checking the return value. SomeERC20tokens are non standard and either return false on failure or return no data.In those cases, a raw transfer can fail silently or revert due to unexpected return data, which undermines the reliability of the admin rescue path.
This is an operator safety issue because it can leave assets stuck or create false confidence that a rescue succeeded.
IERC20(token).transfer(to, amount);Recommendation
Use
SafeERC20.safeTransferfor rescue transfers to handle non standardERC20behavior consistently.Resolution
Valantis Team: The issue was resolved in commit 9f6cc07.
-
I-03 Informational Variable Misuse In Rebase Ops Script Suggestion Acknowledged
Description
The operational
rebasescript will import some environment variables but also declares some internal ones likeRPC,overseerandPRIVATE_KEY.The following issues arise:
- Script uses the env
rpcfor some calls andRPCfor others. That means you can end up with
different
RPCendpoints in the same script.- The
overseeraddress is already declared in the environment, but overwritten in the script (currently
the same address but could introduce bugs if changes)
PRIVATE_KEYis unused, as the ledger is utilized duringcast send
Recommendation
Consider using one single
RPCurl, use the overseer address from env and delete thePRIVATE_KEYif not used.Resolution
Valantis Team: Acknowledged.
- Script uses the env
-
I-04 Informational Redeemable Burns May Still Revert Unexpected Behavior Resolved
Description
The
redeemable()only checks balances/liabilities and does not consider pending slashes or redemption pauses.However,
redeem()enforces!_pendingSlashExists()and pause status. As a result,redeemable()can return true whileredeem()still reverts, misleading UIs/integrations and prompting failed user transactions.Recommendation
Align
redeemable()withredeem()semantics by incorporating the same gating conditions (pendingSlashExistsandisRedemptionPaused) or document thatredeemable()assumes no pending slash and no pause status.Resolution
Valantis Team: Resolved.
-
I-05 Informational Test May Fail Due To Overseer HYPE Balance Warning Resolved
Description
Currently, the
Overseercontract has the following stats:- protocol pending fees 1181 HYPE
- total pending burns 66,817 HYPE
- contract balance 69,145 HYPE
Therefore, when running in a forked environment with the current block, the V3 upgrade test will fail when calculating the
remainingToMintas the contract balance exceeds pending fees + burns, causing an underflow.Keep in mind this is an issue with the test script, not a contract logic issue.
Recommendation
Consider only minting stHYPE if pending fees + burns exceed overseer's balance:
uint contractBalance = address(overseer).balance; uint totalPendingBurns = overseer.totalPendingBurns(); uint protocolPendingFee = overseer.protocolPendingFee(); if(contractBalance < protocolPendingFee + totalPendingBurns) { uint256 remainingToMint = protocolPendingFee + totalPendingBurns - contractBalance; deal(owner, remainingToMint); overseer.mint{value: remainingToMint}(owner); }Resolution
Valantis Team: Resolved.
No findings match.
More from Valantis
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.