Guardian's review of Contract Updates for Bracket, published September 2025. The report records 37 findings across 4 review rounds, including 2 critical and 8 high.
- Published
- Review window
- August 28 to September 18, 2025
- Rounds
- Main Review, Remediation Review, Remediation Review 2, Remediation Review 3
- Language
- Solidity
- Chains
- Ethereum
- Sector
- Yield and vaults
- 2 Critical
- 8 High
- 9 Medium
- 14 Low
- 4 Informational
Scope
Findings 37
Main Review
17 findings · August 28 to September 1, 2025-
C-01 Critical Shares Value Transferred Logical Error Resolved
Description
In the
unwrapfunction the transferFrom invocation transfers theamountspecified by the user, however this is a shares amount rather than a token amount.Recommendation
Use the
assetsvalue in thetransferFrominvocation instead. -
C-02 Critical Double Share Conversion Perturbs Accounting Logical Error Resolved
Description
The overridden
_updatefunction in theRebasingTokencontract has been updated to include a conversion to shares for the amount being transferred. However the vault treats the amount being specified for the transfer/mint/burn action as a share amount.Therefore in the
_clearDepositandwithdrawfunctions when the_mintand_burnfunctions are called with values that have already been converted to shares, these values are doubly converted to shares thus perturbing the accounting system.Recommendation
Consider removing the conversion to shares in the
_updatefunction, otherwise refactor the vault to treat the amounts passed into the transfer/mint/burn to be raw asset amounts rather than share amounts. -
H-01 High Missing Pause Functions Logical Error Resolved
Description
The
BracketWrappedVaultcontract inherits fromPausableUpgradeablewithout implementing functions to expose the pause/unpause functionality.As a result the wrap and unwrap functions cannot be paused.
Recommendation
Implement pause and unpaused permissioned external functions.
-
H-02 High Constructor Used For UUPS Contract Logical Error Resolved
Description
In the
BracketWrappedVaultcontract the constructor is used to initialize the contract with the correct vault, decimals, name and symbol.However the
BracketWrappedVaultcontract is a UUPS implementation contract, therefore this assignment should occur in the initialize function to instantiate the storage slots of the proxy contract being used with this implementation contract. And the constructor of the implementation contract should usedisableInitializersto disable the initialization of the UUPS implementation contract.Recommendation
Move the constructor logic into an initialize function and use
disableInitializersin the constructor of theBracketWrappedVaultcontract. -
H-03 High Missing Ownership Initialization Logical Error Resolved
Description
The
BracketWrappedVaultconstructor fails to call the__Ownable_initfunction of theOwnableUpgradeablebase contract. As a result no owner is assigned for the protocol and upgrades using the UUPS functionality cannot take place.Recommendation
In the new initialize function for the
BracketWrappedVaultcontract, invoke the__Ownable_initfunction and assign the appropriate owner. -
H-04 High Balances Do Not Match Total Supply Logical Error Resolved
Description
The
activeBalanceOfhas been changed to use thevanityAssetsPerSharefunction which maintains it’s original behavior of fluctuating the current reported balance of users by thevanityNav, however theactiveSupplyfunction still uses theassetsPerSharefunction which is now reliant on the reported nav from the previous epoch.As a result the reported values from the summation of user balances and the
totalSupplyresult will not agree when the vanityNav does not match the reported nav from the previous epoch.Recommendation
Update the
activeSupplyfunction to use thevanityAssetsPerSharefunction instead of theassetsPerSharefunction if it is desired that the supply and balances of users float with the vanityNav. -
H-05 High Incongruent Rates Used In Wrapping Logical Error Resolved
Description
In the
BracketWrappedVaulttheconvertToSharesandconvertToAssetsfunctions are used with theassetsPerSharerate to convert to and from shares.However the
assetsPerSharerate is not what will be used to determine how many shares ought to be transferred, instead thevanityAssetsPerSharerate is used in the_updatefunction.This will result in an unexpected amount of shares being transferred when the vanityNav does not match the nav of the last epoch.
Recommendation
Use the
vanityAssetsPerShareinstead of theassetsPerSharein thewrapandunwrapfunctions when converting between shares and assets. -
H-06 High Upgrades Perturb Balances Logical Error Resolved
Description
In the
_updatefunction a conversion to shares by thevanityAssetsPerSharerate has been introduced. This may be fine for new vaults which are being introduced, however for existing vaults this will immediately change the interpretation and valuation of entries in the balances mapping.Recommendation
Do not perform this upgrade for vaults which already exist
-
M-01 Medium Nav Cannot Be Updated From Zero Logical Error Acknowledged
Description
In the
_getDifffunction the_currentNavis always used in the denominator of the basis points diff calculation. As a result, if the nav ever goes to zero for any vault and is then updated to a nonzero value, the update will panic revert in the_getDifffunction due to attempted division by 0.Recommendation
Consider early returning a default value of 0 from the the
_getDifffunction to avoid division by zero in this case. -
M-02 Medium Nav Updates May Be Unintentionally Constricted Validation Acknowledged
Description
The
maxBpsIncreasevalue cannot be assigned higher than 10_000 basis points, meaning that any nav increase cannot reflect a doubling in the price of a bracket vault share.This may inadvertently over-limit the appropriate amount of share value increase that can occur in a given epoch, since it could be reasonable for a fund to double it’s nav in a week long epoch in certain outlier circumstances.
Recommendation
Consider if the 10_000 basis point cap should hold for the increase validation, or if it should be higher.
-
L-01 Low Zero Wraps Allowed Validation Resolved
Description
In the
wrapandunwrapfunctions, amounts of zero are allowed which may lead to unexpected behavior. To limit the unexpected paths that can be taken these values should be validated against.In the wrap function, even non-zero but very small
amountvalues can round to a zeroshareamount, so in thewrapfunction thesharevalue should be validated to be nonzero.Recommendation
In the wrap function, validate that the
sharevalue is not zero, and in theunwrapfunction validate that theamountvalue is not zero. -
L-02 Low Approval Mismatch Unexpected Behavior Resolved
Description
The approval amounts in the rebasingToken contract are tracked and spent based on an asset basis rather than a share basis which leads to a mismatch between the shares which can be transferred relative to the approval amount over time.
For example, if User A approves User B to spend 10 amount at T = 1 when the assets per share is 1, then User B can spend 10 shares from User A.
However if at T = 2 the assets per share is 2, User B can now only spend 5 shares from User A.
This may simply be the expected behavior, since the value of the assets has gone up and so the approval is desired to be in the final amount rather than the shares amount.
However this creates notable behavior when rounding is taken into account, where small approval amounts can be spent without the transferring of actual assets. For example, if the asset per share rate is 1.1e18, a user is approved to spend 10e18 tokens from User A, and they transfer 1 wei of assets from User A the approval will decrease by 1 wei without transferring any assets from User A since the resulting transferred shares value rounds to zero.
Recommendation
Overall these behaviors are innocuous but worth noting and confirming if they are the expected behavior.
-
L-03 Low Misleading Conversion Functions Warning Resolved
Description
In the
RebasingTokencontract theconvertToSharesandconvertToAssetsfunctions which accept only a shares amount have been introduced and are not used anywhere in the codebase. This indicates that they are for usage by integrating protocols or users interacting with the vault.In this case, it should be noted that these functions may be misleading, since they use the
assetsPerSharevalue which is based on the per share rate of the last epoch, which is not what is reflected in the user balances by way of thevanityAssetsPerSharerate being used in theactiveBalanceOffunction.Recommendation
Be aware of this potentially misleading behavior of the
convertToSharesandconvertToAssetsfunctions. -
L-04 Low Missing Closed Case Best Practices Resolved
Description
In the
canDepositandcanWithdrawfunctions there is no case that handles theCLOSEDActionStatenor is there a default return value for the function if none of the explicit cases are matched.Recommendation
Consider adding a default
return falsestatement to silence the compiler warning and implicitly cover theCLOSEDActionStatecase. -
L-05 Low Duplicated Active State Unexpected Behavior Acknowledged
Description
In the
NavUpdatercontract there is an active state tracking for each vault, however this mirrors the active state tracking for each vault already done in theKYCWhitelistcontract.Recommendation
Consider if this state can be deduplicated to simply rely on the
KYCWhitelisttracking of the active state of a vault. -
I-01 Informational Missing Initializers Best Practices Resolved
Description
In the constructor for the
BracketWrappedVaultthe initializer invocations are missing for thePausableUpgradeable, andUUPSUpgradeablecontracts.These initializer functions do nothing, however it is a best practice to call these initializer functions in the event that a future version of these base contracts in a future upgrade would have some logic.
Recommendation
Consider calling the initializer functions for the
PausableUpgradeable, andUUPSUpgradeablecontracts in the newBracketWrappedVaultinitializer function. -
I-02 Informational Dust Left In BracketWrappedVault.sol Warning Acknowledged
Description
Due to the round down conversion in the
convertToAssetsfunction there may be cases where a dust amount of shares are left held by theBracketWrappedVaultcontract after all users have withdrawn.Recommendation
Simply be aware of this behavior.
Remediation Review
16 findings · September 13 to 18, 2025-
H-01 High Vault Router Avoids KYC Logical Error Resolved
Description
The
VaultRouterhas been introduced to allow users to easily wrap WETH and deposit into the vault with. However this contract allows users to bypass the KYC restriction because theBracketVaultperforms validation on themsg.senderin the deposit function, which would be theVaultRoutercontract, which must be whitelisted to function.Recommendation
Either perform the whitelist validation also in the
VaultRoutercontract or consider removing theVaultRoutercontract entirely. -
H-02 High Incorrect assetsPerShare Starting Value Logical Error Resolved
Description
The
BracketVaulthas a decimals amount of whatever the underlying token has and usesconvertToSharesto determine the amount of vault tokens to mint.However the default value for the
assetsPerShareis 1e18 when the epoch is 0, this leads to extreme precision loss and ultimately loss of funds for lower decimal vaults.Recommendation
Report the default
assetsPerShareas10 ** _decimals. Furthermore ensure that the updates for the nav for these vaults is in line with the decimals of the vault to avoid loss of funds and precision loss. -
M-01 Medium Existing Allowances Change Warning Acknowledged
Description
Previously, the
transferFromandtransferfunctions acceptedvalueparameters that representedsharevalues. However now they have been updated to acceptvalueparameters that representassetvalues.Originally, the approvals consumed in
transferFromwould be in shares, however now the approvals consumed intransferFromare based on the providedvalueparameter in assets.So the net effect on approvals of this upgrade for existing vaults is that the approval values made by users will be changed from share values to asset values. Depending on the NAV of the vault this will either increase or decrease the amount transferrable by approvals for users.
Recommendation
Be aware of this change and ensure it will not cause any significant unexpected changes for users. Users should be aware of this change as well.
-
M-02 Medium Balances Do Not Match Total Supply Logical Error Resolved
Description
The
balanceOffunction has been updated to only rely on theactiveBalanceand ignore theinactiveBalance.This is however contradictory to the implementation of the
totalSupplyfunction which reports the sum of theactiveSupplyandinactiveSupplyas thetotalSupply.As a result the sum of the individual user balances does not match the aggregate totalSupply reported by the
BracketVault.Recommendation
If it is desired to only factor in the active shares, then update the
totalSupplyfunction to only track theactiveSupply. -
M-03 Medium KYCWhitelist Storage Corruption Logical Error Acknowledged
Description
The
KYCWhitelistcontract is aUUPSUpgradeableimplementation contract, however storage variables have been added before theisUsedNoncemapping which will change the storage values that it is mapped to.This will reset all of the
usedNoncereporting and allow replay of these signatures.Recommendation
Only append new storage variables after the
isUsedNoncemapping. -
M-04 Medium Ineffective Blacklist Logical Error Resolved
Description
The
KYCWhitelistnow has a blacklist mode, whereby only blacklisted addresses are considered not whitelisted for deposits and withdrawals.However this blacklist is easily bypassed by a blacklisted user by transferring their deposit tokens or bracket vault tokens to a non-blacklisted address they control and performing the desired actions with the vault.
Recommendation
Be aware of this loophole and consider performing transfer checks if the KYC whitelist is in blacklist mode.
-
M-05 Medium Incorrect totalPendingShares Tracking Logical Error Acknowledged
Description
In the
BracketVaultcontract thetotalPendingSharesvariable is tracked to represent the total sum of all pending shares which technically belong to users but have not yet been minted to their underlying balances mapping entry.The use case for this variable is to ensure that the
totalSupplyreported is the same as the sum of the user balances. However the tracking of thetotalPendingSharesdoes not correctly allow this.The
totalPendingSharesvariable is adjusted for withdrawals, however this has nothing to do with the pending shares that should be minted to users via deposits and thus perturbs thetotalSupplyaccounting. For example, the totalSupply is based on thetotalMintedShares()) + totalNonMintedShares()result.totalNonMintedSharessimply returnstotalPendingSharesfor theBracketVaultcontract.Whenever a withdrawal is initiated, it is both burned from the
totalMintedSharesand added to thetotalPendingShares. This means there is no net change in the reportedtotalSupply, which does not agree with the underlying user balances tracking.Furthermore, the
totalPendingSharesdeduction by thewithdrawalSharesin the_processDepositsWithdrawalsfunction is also invalid as this has no impact on the pending deposit shares of users.Recommendation
Remove the deduction of
withdrawalSharesfrom thetotalPendingSharesin the_processDepositsWithdrawalsfunction.And remove the addition of
sharesto thetotalPendingSharesin the withdraw function. -
M-06 Medium Whitelist Can Be Opened By Any Whitelisted User Gaming Acknowledged
Description
With the advent of EIP 7702 accounts can now create code at their own address. This means that any whitelisted user can open up the whitelisted functionality to be used by non-whitelisted users.
Consider the following scenario:
- Assume protocol has a whitelist mapping, where only users who are whitelisted can call function A
- Bob is whitelisted for his address 0xFF
- Bob uses 7702 to set his 0xFF account code to:
contract { address victimSystem; function callThis(...) external { victimSystem.whitelistedFunction(...); } }- Now anyone can call the whitelisted function through calling callThis on 0xFF
Recommendation
Be sure to monitor whitelisted accounts to ensure that they do not add functionality that opens up the whitelist to non-whitelisted accounts. Otherwise consider requiring that
msg.sender == tx.originfor the whitelist. -
L-01 Low User Balances Stepwise Update Warning Acknowledged
Description
The
assetsPerSharefunction has been updated to use the NAV value of the last epoch rather than thevanityNavof the current epoch.As a result, for existing vaults this will cause a stepwise jump in the balance reported to users and the reported totalSupply.
This may cause unexpected issues for integrations which may rely on this data and may be unexpected for users.
Recommendation
Be sure to document this change for integrators and users who may be impacted.
-
L-02 Low Lock Frequency And Withdraw Delay Validation Validation Resolved
Description
The lock frequency was previously hardcoded at 7 days, however now it is configurable by the admin lite role and upon initialization.
The vault could however benefit from some validations on the frequency to ensure that it is neither configured to long or too short.
For example, the
BracketVaultcontract will stop functioning aftertype(uint16).maxepochs, therefore an epoch time of 10 minutes is too low.Similarly, there is no validation that the configuration of the withdrawDelay is greater than some minimum and less than some maximum.
Recommendation
Consider if validations should be introduced which ensure the
lockFrequencyandwithdrawDelayis bounded between a maximum and minimum range on initialization and updates. -
L-03 Low No Way To Update VanityNav Warning Acknowledged
Description
With the new
NavUpdatercontract there is no way to update thevanityNavwith theupdateVanityNavfunction as this function is not invoked in theNavUpdatercontract.Recommendation
Be aware of this unreachable function.
-
L-04 Low Unused Modifier Superfluous Code Resolved
Description
In the
NavUpdatercontract theonlyActiveVaultmodifier is unused.Recommendation
Consider implementing it’s use or removing it.
-
L-05 Low NavUpdater Deployment Warning Warning Acknowledged
Description
The
NavUpdaterhas an open initialize function which can be called by anyone and used to grant the caller the default admin role of the contract.If the NavUpdater has already received the
NAV_UPDATER_ROLEfor any vault instances a malicious actor could initialize it and unexpectedly start vaults or issue nav updates for existing vaults.Recommendation
Be sure to initialize the
NavUpdatercontract before it receives any permissions. Furthermore, ideally theNavUpdatercontract is initialized in the same transaction as it’s deployment, or at least in the same deployment script. -
L-06 Low Balances No Longer Include Inactive Amounts Warning Acknowledged
Description
In the
RebasingTokenbalanceOffunction, theinactiveBalanceOfcontributor has been removed, which will cause a stepwise change in the balances of users and Smart Contracts which have pending deposits.This may be unexpected and could potentially cause issues in integrating systems.
Recommendation
Be sure to warn any integrators and users of this impending stepwise change.
-
L-07 Low totalSupply Rounding Rounding Acknowledged
Description
After the balances and totalSupply mismatch is resolved from M-02,
Balances Do Not Match Total Supply, there remains a rounding error which invalidates thesum of user balances == totalSupplyinvariant.This is due to rounding which occurs upon converting between users shares and asset amounts on transfers and deposit/withdrawals and on the calculation of user balances and totalSupply.
Extensive fuzz testing has shown that the inaccuracy can only occur in the direction of
sumOfUserBalances ≤ totalSupply, and is at maximum off on the order of thousands of wei.Recommendation
Be aware of this rounding difference and invalidation of the
sum of user balances == totalSupplyinvariant. -
I-01 Informational Unnecessary Import Superfluous Code Resolved
Description
In the
BracketWrappedVaultcontract theIERC20interface is explicitly imported but not used.Recommendation
Consider removing the
IERC20interface import.
Remediation Review 2
2 findings · September 16, 2025-
M-01 Medium Whitelist Can Be Opened By Any Whitelisted User Gaming Acknowledged
Description
With the advent of EIP 7702 accounts can now create code at their own address. This means that any whitelisted user can open up the whitelisted functionality to be used by non-whitelisted users.
Consider the following scenario:
- Assume protocol has a whitelist mapping, where only users who are whitelisted can call function A
- Bob is whitelisted for his address 0xFF
- Bob uses 7702 to set his 0xFF account code to:
contract { address victimSystem; function callThis(...) external { victimSystem.whitelistedFunction(...); } }- Now anyone can call the whitelisted function through calling callThis on 0xFF
Recommendation
Be sure to monitor whitelisted accounts to ensure that they do not add functionality that opens up the whitelist to non-whitelisted accounts. Otherwise consider requiring that
msg.sender == tx.originfor the whitelist. -
I-01 Informational canTransfer Also Impacts Deposits And Withdrawals Warning Acknowledged
Description
The
KYCWhitelistcanTransferfunction has been added to the_updatefunction and is now validated upon every invocation of_update. This means that thecanTransfervalidation applies for mints and burns when the from or to address is zero.This may be unexpected given the naming of the predicate function,
canTransfer, however seems appropriate given that this is how the blacklist is enforced.Recommendation
Consider if it is expected that the
canTransfervalidation is performed on mints and burns.
Remediation Review 3
2 findings · September 17, 2025-
L-01 Low Withdrawals May Be Missed If Withdrawal Delay Is Updated Unexpected Behavior Acknowledged
Description
In the
fixTotalNonMintedSharesfunction thesumWithdrawalsvalue only increments until themaxEpochwhich is assigned ascurrentEpoch + withdrawDelay.However if the
withdrawDelayis updated it may be possible that withdrawals exist past the definedmaxEpochand may be missed by the correction.Recommendation
Be sure that the
maxEpochis in line with the latest withdrawal for each vault when thefixTotalNonMintedSharesfunction is invoked. -
L-02 Low fixTotalNonMintedShares Can Be Called Again Warning Acknowledged
Description
The
fixTotalNonMintedSharesfunction can be called again when the pending non minted shares return to zero. And for vaults which are never “fixed” this function can be called unintentionally.Recommendation
This function is gated by the admin lite role so it is trusted. Simply be aware of this quirk.
No findings match.
More from Bracket
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.
