As of 3 Nov 2026, Hifi Finance has 1 audit report on Trustblock, by Quantstamp, dated 21 Oct 2022. That report lists no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Hifi Finance | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 1 | 9 | 5 |
| Low | 1 | 13 | 2 |
The `Fintroller` contract defines a debt `ceiling` for every `Bond` added. The following code within the `BalanceSheetV2.borrow()` implies that the total debt is the supply of the hToken corresponding to the bond: ``` uint256 newTotalSupply = bond.totalSupply() + borrowAmount; uint256 debtCeiling = fintroller.getDebtCeiling(bond); if (newTotalSupply > debtCeiling) { revert BalanceSheet__DebtCeilingOverflow(newTotalSupply, debtCeiling); } ``` However, it is possible to increase the supply of the hToken past the debt ceiling by calling the `HToken.depositUnderlying()` function. This function mints new hTokens so long as the equivalent amount of underlying is supplied. There are no checks to ensure that hTokens will not be minted past the debt ceiling.
Open the reportThe `BalanceSheetV2` includes functions for repaying debt (`repayBorrow()`, `repayBorrowBehalf()`) as well as liquidating debt (`liquidateBorrow()`). All these functions will revert if the user attempts to repay more than the vault's current debt. The code below causes a revert when a user attempts to repay more than the vault's debt. Similar logic applies to liquidations. ``` uint256 debtAmount = vaults[borrower].debtAmounts[bond]; if (debtAmount < repayAmount) { revert BalanceSheet__RepayBorrowInsufficientDebt(bond, repayAmount, debtAmount); } ``` The logic above can lead to a denial of service for liquidators or borrowers trying to repay their loans. If a user tries to repay the total amount of their debt, they may be front-run by a malicious liquidator, which calls `repayBorrowBehalf()` with a minimal repayment. The transaction for the normal user will revert because the amount they are attempting to repay exceeds the vault's debt by a small amount. A similar scenario can occur when a malicious user prevents liquidation by front-running a valid call to `liquidateBorrow()` with either a small repayment or a small liquidation.
Open the reportEach collateral has its own liquidation incentive, and the owner can change this value using the `setLiquidationIncentive()` function. The issue here is that before running the liquidation, a change request can be made by the owner to front-run the liquidation transaction. The worst-case scenario is that the user will receive less than what was expected.
Open the reportThe `HifiProxyTarget` features many functions which either add or remove liquidity from the `HifiPool` contract. All of these functions are missing sufficient protection against slippage. Liquidity providers are subject to impermanent loss when the interest rate for the `HifiPool` changes. The interest rate depends on the size of the hToken and underlying reserves. Liquidity providers must provide the underlying tokens and hTokens in their desired expected ratio. They should also always receive the amounts of tokens they expect when removing liquidity if they do not wish to realize an impermanent loss. There are some functions within the `HifiProxyTarget` which provide some slippage protection such as `addLiquidity()` which requires a `maxHTokenRequired` parameter. This protects the price of the hToken going up. However, there is no protection provided against the price of the underlying token (relative to the hToken) going up. To sufficiently protect against slippage, a `minHTokenRequired` parameter should be used as well. Similarly, functions that remove liquidity, such as `removeLiquidity()`, lack parameters to indicate the minimum amount of hTokens and underlying tokens received. We have listed all functions on the `HifiProxyTarget` which lack sufficient slippage protection: * `addLiquidity()` * `addLiquidityWithSignature()` * `borrowHTokenAndAddLiquidity()` * `borrowHTokenAndAddLiquidityWithSignature()` * `buyHTokenAndAddLiquidity()` * `buyHTokenAndAddLiquidityWithSignature()` * `buyUnderlyingAndAddLiquidity()` * `buyUnderlyingAndAddLiquidityWithSignature()` * `depositCollateralAndBorrowHTokenAndAddLiquidity()` * `depositCollateralAndBorrowHTokenAndAddLiquidityWithSignature()` * `depositUnderlyingAndMintHTokenAndAddLiquidity()` * `depositUnderlyingAndMintHTokenAndAddLiquidityWithSignature()` * `removeLiquidity()` * `removeLiquidityAndRedeem()` * `removeLiquidityAndRedeemWithSignature()` * `removeLiquidityAndSellHToken()` * `removeLiquidityAndSellHTokenWithSignature()` * `removeLiquidityAndWithdrawUnderlying()` * `removeLiquidityAndWithdrawUnderlyingWithSignature()` * `removeLiquidityWithSignature()`
Open the reportThe `getBurnOutputs()` function calculated the number of `hTokensReturned` using the following formula ``` hTokenReturned = (poolTokensBurned * hTokenReserves) / supply; ``` If the `poolTokensBurned * hTokenReserves` is less than `supply`, `hTokenReturned` will be equal to `0` due to a loss of precision.
Open the reportThe owner has the ability to add a new bond token using the `listBond()` function. This will create the new bond with the default parameters. However if the owner injected an existing bond he will override the old bond.
Open the reportAs a best practice and to prevent unwanted external contract side effects, it is advised to adhere to the ["Checks-Effects-Interactions"-pattern](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html), even in the presence of [reentrancy guards](https://docs.openzeppelin.com/contracts/4.x/api/[](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html)security#ReentrancyGuard). Following instances were observed where said pattern was violated: - `HifiProxyTarget.addLiquidity()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.borrowHTokenAndAddLiquidity()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.buyHToken()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.buyHTokenAndAddLiquidity()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.buyHTokenAndRepayBorrow()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.depositCollateral()`: Performs external contract calls (`collateral.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.depositUnderlyingAndMintHTokenAndAddLiquidity()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.removeLiquidity()`: Performs external contract calls (`hifiPool.underlying().safeTransfer()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.removeLiquidityAndRedeem()`: Performs external contract calls (`hToken.underlying().safeTransfer()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.removeLiquidityAndSellHToken()`: Performs external contract calls (`hifiPool.underlying().safeTransfer()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.sellUnderlyingAndRepayBorrow()`: Performs external contract calls (`underlying.transferFrom()`), before modifying state variables in following sub-calls. - `HifiProxyTarget.depositUnderlyingInternal()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls. - `HifiPool.sellUnderlying()`: Performs external contract calls (`underlying.safeTransferFrom()`), before modifying state variables in following sub-calls.
Open the reportAn early liquidity provider can manipulate a single share's price to raise it high enough such that only the largest providers can afford to add liquidity. The share price can be manipulated by first only adding a tiny amount of liquidity and then directly transferring a large amount of the underlying token to the pool.
Open the reportThe contract approves an address to transfer tokens on its behalf without setting a limit of how many tokens may be transferred (`token.approve(spender, type(uint256).max);`). If the approved address becomes hacked or is intentionally malicious, it may transfer out all the approved tokens.
Open the reportFunction `ChainlinkOperator.setFeed()` saves new feed information in storage variable `mapping(string => Feed) internal feeds;`, which is indexed by the assets symbol string. Trying to add different assets with the same symbol string would lead to collisions in said array, overwriting the existing asset.
Open the reportThe clone-and-own approach involves copying and adjusting open source code at one's own discretion. From the development perspective, it is initially beneficial as it reduces the amount of effort. However, from the security perspective, it involves some risks as the code may not follow the best practices, may contain a security vulnerability, or may include intentionally or unintentionally modified upstream libraries. The `OwnableUpgradeable` contract is a fork of an OpenZeppelin contract. We identified a separate vulnerability in the report due to not using the latest OpenZeppelin contract. That vulnerability was the lack of inclusion of a storage gap which is included in the latest OpenZeppelin contracts.
Open the reportSeveral instances of events are being emitted after external calls within the contracts of the protocol. If the functions were re-entered due to external calls, this could result in events being emitted out of order. We have provided a non-exhaustive list of such event emissions below: * `HifiPool` * `burn()`: Emits `RemoveLiquidity` after external calls. * `buyHToken()`: Emits `Trade` after external calls. * `buyUnderlying()`: Emits `Trade` after external calls. * `mint()`: Emits `AddLiquidity` after external calls. * `sellHToken()`: Emits `Trade` after external calls. * `sellUnderlying()`: Emits `Trade` after external calls. * `BalanceSheet` * `borrow()`: Emits `Borrow` after external calls. * `depositCollateral()`: Emits `DepositCollateral` after external calls. * `withdrawCollateral()`: Emits `WithdrawCollateral` after external calls. * `HToken` * `depositUnderlying()`: Emits `DepositUnderlying` after external calls. * `mint()`: Emits `Mint` after external calls. * `withdrawUnderlying()`: Emits `WithdrawUnderlying` after external calls.
Open the reportThe Hifi protocol allows for pausing numerous operations for collateral and debt assets through various functions within the `Fintroller` contract. However, many other critical operations are not pausable. While we realize that there is always a trade-off between centralization and stronger pause functionality, the Hifi team should consider adding the ability to pause the following: * Bond/debt operations * Withdraw underlying deposited within the `HToken` contract. This would prevent an attacker from withdrawing maliciously obtained hTokens. * Collateral operations * Withdrawal of collateral stored within the `BalanceSheetV2` contract. This could prevent an attacker from stealing or withdrawing excess collateral. * Liquidation of collateral assets stored within the `BalanceSheeetV2` contract. While the ability to pause operations on particular bond assets has been added, there is no way to disable the liquidation of a particular collateral asset. This may be desirable if the oracle temporarily delivers incorrect prices for a specific asset. * Prevent collateral from counting towards the user's total collateral value. If the team deems a collateral asset too dangerous to continue using, they may consider not allowing it to count towards a user's collateral value. Another mitigation would be raising the collateral ratio to the maximum value. * Trading operations * No pause functionality has been added for the AMM portion of the protocol. The team could consider adding functionality to pause adding/removing liquidity and trading functions.
Open the reportSeveral functions within the protocol lack sufficient validation of their input parameters. We have listed all missing checks below: * `HifiPool` * `constructor()`: Validate that the `string` parameters are not empty. * `buyHToken()`: Validate that `_to` is not the zero address. * `buyUnderlying()`: Validate that `_to` is not the zero address. * `sellHToken()`: Validate that `_to` is not the zero address. * `sellUnderlying()`: Validate that `_to` is not the zero address. * `BalanceSheetV2` * `withdrawCollateral()`: Validate that `collateral` is listed within the `Fintroller`. * `HToken` * `constructor`: Validate that the `string parameters are not empty. * `ChainlinkOperator` * `setFeed()`: Validate that `asset` has `18` or fewer decimals. * `HifiProxyTarget` * `sellHToken()`: Validate that `minUnderlying` is greater than zero. * `sellHTokenWithSignature()`: Validate that `minUnderlying` is greater than zero. * `sellUnderlying()`: Validate that `minHTokenOut` is greater than zero. * `sellUnderlyingAndRepayBorrow()`: Validate that `minHTokenOut` is greater than zero. * `sellUnderlyingAndRepayBorrowWithSignature()`: Validate that `minHTokenOut` is greater than zero. * `sellUnderlyingWithSignature()`: Validate that `minHTokenOut` is greater than zero.
Open the reportIf the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed.
Open the reportMany contracts within the protocol feature a contract owner who has special privileges. We recognize that the Hifi team is in the early stages of progressive decentralization. However, all privileged permissions should be clearly documented for users. We have listed all the privileged permissions of the contract owners below: * `BalanceSheetV2` * The `fintroller` contract can be reset anytime, effectively resetting the whole protocol's configuration. * The `oracle` contract can be reset anytime, potentially changing the prices used for collateral and debt assets. * `Fintroller` * A new `Bond` can be listed at any time. * A new `Collateral` can be listed at any time. * The `Collateral` and `Bond` ceilings can be reset anytime, potentially increasing the protocol's risk exposure to a particular asset. * A collateral asset's collateral ratio and liquidation incentive can be reset anytime. * Collateral deposits can be enabled and disabled at any time. * Debt repayments, borrows, and liquidations can be disabled and enabled at any time. * The maximum number of bond assets per vault can be reset at any time. Setting this value too high could result in critical functions failing due to excessive gas usage. * `HToken` * The `balanceSheet` contract referenced can be reset at any time. * Transfer an arbitrary amount of any token from the contract, except the underlying token. * `ChainlinkOperator` * Price feeds can be added or removed at any time.
Open the reportThe protocol currently only allows adding tokens as collateral or underlying for HTokens with up to 18 decimals and not more. While in practice most tokens only use up to 18 decimals, this constraint could become a limitation in the future, especially given that this value is hardcoded and cannot be easily modified.
Open the reportNumerous contracts within the contract either inherit from PRB contracts or use PRB libraries. These contracts are not within the scope of the audit and have not been previously audited [as stated by the author](https://github.com/paulrberg/prb-contracts#security). These contracts might have existing unfound vulnerabilities which could compromise the security of the Hifi protocol.
Open the reportSeveral contracts within the protocol make external calls to functions with return values which indicate the result of those functions. However, many of these return values are not being validated within the code. We have listed all function calls for which return values are being ignored: All Calls to HifiPool.buyHToken() All Calls to HifiPool.buyUnderlying() All Calls to HifiPool.sellHToken() All Calls to HifiPool.sellUnderlying()
Open the reportTo prevent confusion and the introduction of vulnerabilities, test code should not be present in production code/should be clearly separated.
Open the reportThe various `initialize()` functions of upgradeable contracts are not constructors. There is a low-but-not-zero chance that someone can call these after the contracts have been deployed, but before the development team calls them. Consequently, contracts may get initialized with values that are not desirable by the development team.
Open the reportThe `getQuoteForBuyingHToken()` function is referenced by the `buyHToken()` function to determine how many underlying tokens are needed to buy a certain amount of hTokens. However, the resulting amount of underlying tokens is denormalized: `underlyingIn = denormalize(normalizedUnderlyingIn);`. This results in any amount of underlying token below `underlyingPrecisionScalar` essentially being discounted from the price. The result could be significant if the underlying token has much fewer decimals than `18` and the value of `1` full token is high. For example, in the case of WBTC, a user could receive up to `1` Satoshi worth of a discount. A similar issue exists in `getQuoteForSellingHToken()`, which results in a user paying more hTokens than they should for a certain amount of underlying tokens.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="mainframe"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us