Hyperbolic Protocol
Smart Contract Audit Report
Audit Summary
Hyperbolic Protocol is a 100% on-chain collateralized lending protocol that is intended to leverage Uniswap V3 TWAPs, automated variable APRs and protocol fees to yield rewards to investors.
For this audit, we reviewed the following contracts at commit f9a67070bb7d05b01c5d74c89b009686e2e66325 on the team's private GitHub repository.
We previously reviewed the project team's GudGuess platform here.
Audit Findings
Low findings were identified and the team should consider resolving these issues. In addition, centralized aspects are present.
Date: April 20th, 2023.
Updated: May 16th, 2023 to reflect changes from commit e0152b02b4cf015a6861ec6b3ad9cb1679a65101 to commit e9a9a0eca522f7aef518398523efe570d69ef80b on the team's private GitHub repository. Additionally, the platform now intends to utilize the TwapUtils contract at address 0xdf9a6debb35be847d6addb7843e763539671b2c7 on the Arbitrum Mainnet.
Updated: June 8th, 2023 to reflect changes from commit e9a9a0eca522f7aef518398523efe570d69ef80b to commit 78720c7011ffb4cde058d2a9f0b093a9630d1f3e on the team's private GitHub repository.
Updated: June 23rd, 2023 to reflect changes from commit 78720c7011ffb4cde058d2a9f0b093a9630d1f3e to commit 120b5733b016e8126348640a099a979eb3618b13 on the team's private GitHub repository.
Updated: June 26th, 2023 to reflect changes from commit 120b5733b016e8126348640a099a979eb3618b13 to the Ethereum mainnet.
Updated: February 21st, 2024 to reflect changes from the Ethereum mainnet to commit c6470913bbb94c0f95e29417b955bcb1c0f61588 on the team's private GitHub repository.
Updated: March 8th, 2024 to reflect changes from commit c6470913bbb94c0f95e29417b955bcb1c0f61588 to commit f9a67070bb7d05b01c5d74c89b009686e2e66325 on the team's private GitHub repository.Finding #1 - LendingPool - High (Resolved)
Description: The _withdraw() function allows users to withdraw funds from their position if their resulting borrowed to collateral ratio exceeds the maximum loan percentage.
Risk/Impact: A user can drain value from the platform by withdrawing their collateral without repaying their debt.require( _currentLTVX96 == 0 || _currentLTVX96 >= (FixedPoint96.Q96 * maxLoanToValue) / DENOMENATOR, 'WITHDRAW: exceeds max LTV' );
Recommendation: The above require statement should be updated to the following:
Resolution: The team has implemented the above recommendation.require(_currentLTVX96 <= (FixedPoint96.Q96 * maxLoanToValue) / DENOMENATOR, 'WITHDRAW: exceeds max LTV' );
Finding #2 - LendingPool - High (Resolved)
Description: If a user withdraws a partial amount of their position's total deposited tokens and the position does not have an active loan, their LoanToken NFT and associated position is burned.
Risk/Impact: If a user withdraws less than the total collateral from an existing position without an active loan, they will lose the position's remaining collateral to the contract.(uint256 _currentLTVX96, , ) = getLTVX96(_tokenId); require( _currentLTVX96 == 0 || _currentLTVX96 >= (FixedPoint96.Q96 * maxLoanToValue) / DENOMENATOR, 'WITHDRAW: exceeds max LTV' ); if (_currentLTVX96 == 0) { _deleteLoan(_tokenId); }
Recommendation: A position should only be deleted if a position has no remaining collateral or debt.
Resolution: A position is now only deleted if the user has no remaining collateral.
Finding #3 - LendingPool - High (Resolved)
Description: Underflow can occur if a user repays more than the sum of their borrowed amount and interest fees.
Risk/Impact: A position's borrowed amount can underflow and result in a larger value than intended. The user will then not be able to pay their loan back and will be able to be liquidated._loan.amountETHBorrowed -= (_amount - _amountAPRFees);
Recommendation: The mentioned code should be updated to the following:
Resolution: The team has implemented the above recommendation.if ((_amount - _amountAPRFees) > _loan.amountETHBorrowed) { _loan.amountETHBorrowed = 0; } else { _loan.amountETHBorrowed -= (_amount - _amountAPRFees); }
Finding #4 - LendingPool - Medium (Resolved)
Description: Swaps in the _processLiquidatedTokens() function currently use the loan's total borrowed amount as the minimum amount to receive. If a pool's minimum collateral ratio is significantly larger than 100%, the amount received should be expected to be greater than the loan's borrowed amount.
Risk/Impact: A large disparity between the value of the tokens and the current specified minimum amount to receive can allow for frontrunning opportunities in which the contract would receive less funds than they should when a liquidation occurs.
Recommendation: The project team should use the referenced TWAP with desired slippage in order to calculate minimum expected amounts to receive.
Resolution: The team implemented the above recommendation.
Finding #5 - LendingPool - Medium (Resolved)
Description: Users will have to pay lifetime interest on the loan's existing debt each time they borrow or repay. For example, if a user repays half of their loan, they first pay interest on the total amount borrowed for the entire duration of the loan. If they then repay the remaining half, they are charged interest on the remaining half for the entire duration of the loan instead of from the time of the last repayment.
Risk/Impact: Users will pay more interest if executing multiple borrows or repayments within a single position than if they borrowed or repaid the total amount at once.
Recommendation: A Position's aprStart should be updated whenever its interest is paid.
Resolution: The team has implemented the above recommendation.
Finding #6 - LendingPool - Medium
Description: Each collateral token can be given a maxBorrowPerPool value. This prevents the total amount of ETH borrowed from the platform for this collateral token from exceeding a specified value. The contract also liquidates a loan if its LTV exceeds the liquidationLTV value. When liquidating a loan its assets can be swapped for ETH to repay the liquidated loan. However, the currentBorrowPerPool is not decremented when the loan's underlying collateral is liquidated.
Risk/Impact: If enough loans for a specific collateral are liquidated and the collateral has a maxBorrowPerPool value set, no users will be able to borrow using that collateral.
Recommendation: When liquidating, the team should decrement the currentBorrowPerPool by the minimum of either the loan's borrowed ETH amount or the amount of ETH received.
Resolution: The team not yet addressed the above issue.
Finding #7 - LendingPool - Medium
Description: The contract liquidates a loan if its LTV exceeds the liquidationLTV value. When liquidating a loan its assets can be swapped for ETH to repay the liquidated loan. However, if the collateral is HYPE tokens or the owner has disabled liquidating, the loan's collateral is sent to the owner rather than being swapped for ETH.
Risk/Impact: The liquidated loan will never repay its borrowed ETH. This will lead to the contract accumulating bad debt over time as well as the currentBorrowPerPool remaining elevated after each liquidation that is not swapped for ETH.
Recommendation: The team should always liquidate a loan's collateral for ETH.
Resolution: The team not yet addressed the above issue.
Finding #8 - LendingPool - Low
Description: Users are issued a LoanToken NFT to represent their position in the LendingPool contract. These NFTs are transferrable between two users. If a user is transferred a loan with HYPE as the collateral and then deposits additional HYPE tokens, the original user who created the loan is given credit for the HYPE tokens in the RewardsTracker contract.Risk/Impact: An uninformed user may deposit additional HYPE tokens into a loan they were transferred. They would then not receive any rewards from their deposited tokens.if (_token0 == _WETH) { ERC20 _t1 = ERC20(_uniPool.token1()); _t1.safeTransferFrom(_wallet, address(this), _amount); _t1.approve(address(custodian), _amount); custodian.process(_t1, _loan.createdBy, _amount, false); } else { ERC20 _t0 = ERC20(_token0); _t0.safeTransferFrom(_wallet, address(this), _amount); _t0.approve(address(custodian), _amount); custodian.process(_t0, _loan.createdBy, _amount, false); }
Recommendation: The team should consider updating a user's shares in the RewardsTracker when a loan is transferred. Alternatively, a check can be implemented that the loan createdBy address is the same as the depositing _wallet address.
Resolution: The team not yet addressed the above issue.
Finding #9 - LendingPool & HyperbolicProtocol - Informational
Description: The LendingPool contract relies on the TwapUtils contract for pricing in the getLTVX96() and _processLiquidatedTokens() functions. The HyperbolicProtocol contract relies on the TwapUtils for pricing in the poolBalToMarketCapRatio() function. The TwapUtils contract returns pricing data in the Q96 format. This means an additional factor of 2^96 is factored into all values. The TwapUtils getPriceX96FromSqrtPriceX96() function used in the LendingPool and HyperbolicProtocol contracts can return a value with a maximum of 68 decimals. If this size of value were to be reached, a user would only have to borrow ~1 billionth of an ETH to cause an overflow issue in the LendingPool. The HyperbolicProtocol totalSupply would need to be of similar magnitude to cause an overflow.
Recommendation: While these values are not realistic, an overflow is theoretically possible. The team should consider removing the Q96 factor returned from the TwapUtils and multiplying in precision as needed.
Finding #10 - LendingPool - Informational
Description: The contract liquidates a loan if its LTV exceeds the liquidationLTV value. However, the liquidationLTV value is the same for all collateral.
Recommendation: The team should consider adding the ability to add a custom liquidationLTV value for a specified asset. This allows the team to make a lower or higher liquidation threshold based on the volatility of a given asset. This can help prevent the protocol from acquiring bad debt.
Finding #11 - LendingPool - Informational
Description: The contract liquidates a loan if its LTV exceeds the liquidationLTV value. When liquidating a loan its assets can be swapped for ETH to repay the liquidated loan. However, all of the liquidated ETH remains in the contract. This results in no incentive for users outside of the team to liquidate a position.
Recommendation: The team should consider sending the liquidating user a portion of the liquidated ETH. This results in an incentive for any user to liquidate a bad position. This can result in an overall healthier platform as well as allowing users to liquidate if the team is unable to.
Contracts Overview
LoanToken Contract:
- The project team should exercise caution and disallow fee-on-transfer tokens from being used as collateral unless the proper exemptions are made.
- As the contracts are implemented with Solidity v0.8.x, they are protected from any possible overflows/underflows.
HLP Contract:
- This contract defines Hyperbolic Protocol Loan NFTs.
- The associated Lending Pool address can mint or burn a NFT from any user at any time.
- This contract also implements the EIP-2981 NFT Royalty standard to support paying NFT creators a royalty on NFT Marketplaces.
- The owner may update the Royalty address and Royalty basis points at any time.
- The owner may update the base URI at any time.
- The owner can update the Lending pool address at any time.
LendingPool Contract:
- This contract is used to represent users' deposited liquidity into the LendingPoolExtHLP contract.
- The Lending Pool address can mint or burn tokens from any user at any time.
- An associated LendingRewards contract is created by this contract upon deployment.
- Users' Rewardshares are updated in the LendingRewards contract whenever participating in a transfer.
- This contract complies with the ERC-20 standard.
LendingPoolExtHLP Contract:
- This contract allows users to deposit various collateral tokens in order to borrow ETH.
- A user can deposit any supported collateral token by specifying its corresponding liquidity pool address.
- The collateral can be used for a new position, or can be added to an existing position.
- When creating a new position, the user is minted a LoanToken NFT to represent their loan data.
- If adding to an existing position, the user specifies a LoanToken ID that they own to signal the position to be deposited into.
- Deposited funds are transferred into the contract's associated LendingPoolTokenCustodian contract.
- Users can then borrow ETH up to a maximum loan percentage of the ETH value of the collateral token.
- If a user attempts to borrow more than they are permitted, their maximum permitted amount is borrowed instead.
- The maximum loan percentage is determined by the collateral's custom max LTV. If a max LTV is not set, the contract's default maximum LTV is used instead.
- The total amount borrowed from a pool at once cannot exceed the pool's maximum total borrow limit if one has been set for the pool.
- Token prices are calculated using the associated TwapUtils contract.
- A borrowing fee percentage is taken from any ETH borrowed by a user.
- When borrowing from an existing position, the borrowed amount is deducted by any accumulated interest owed.
- Interest is calculated based on the total lifespan of the LoanToken NFT, amount borrowed, and the LendingPool balance to target market cap ratio.
- A low LendingPool balance to target market cap ratio results in a higher APR, decreasing to the contract's minimum APR once the balance has reached the target.
- A user can repay borrowed ETH at any time.
- Each time a user repays, they are first charged interest on the position's current borrowed amount.
- Any remaining funds after interest are counted towards the repayment of borrowed funds.
- Any excess ETH provided is returned to the user.
- A user can withdraw their deposited funds if it does not result in their position's borrowed to collateral ratio exceeding the maximum loan percentage.
- All collected fees and interest are deposited into the LendingRewards contract.
- Any position can be liquidated by any address if its borrowed to collateral ratio exceeds the liquidation threshold.
- When a liquidation using non-HYPE tokens as collateral occurs, the loan's collateral is swapped for the blockchain's native token and stored in the contract for future borrowing as long as the "Liquidate Default Capital" flag is enabled.
- If a loan using HYPE as collateral is liquidated, the loan's collateral is transferred to the contract's owner.
- If the Liquidate Default Capital flag is disabled, any liquidated loan's collateral is transferred to the contract's owner.
- The owner can toggle the Liquidate Default Capital flag at any time.
- The owner can update the swap slippage used for liquidations at any time.
- The owner can update the borrowing fee to any value up to 30% at any time.
- The owner can update the minimum APR to up to 10% and the maximum APR to up to 100% at any time.
- The owner can set a maximum borrow limit for a pool at any time.
- The owner can update the contract's default maximum loan percentage, any custom token maximum loan percentages, and liquidation threshold values to up to 100% at any time.
- The owner can pause deposit and borrow functionality at any time.
- The owner can add or remove a supported liquidity pool from the whitelist at any time.
- A pool must support the UniswapV3Pool interface and contain WETH as one of its tokens in order to be whitelisted.
- The project team should ensure that the liquidation threshold is set to a lower threshold than a token's maximum loan percentage threshold to prevent immediate liquidations after borrowing.
- The project team should ensure that liquidation eligibility is monitored and the liquidation threshold is set to a value less than 100% to avoid losses and to ensure that swaps on liquidations do not fail.
HyperbolicProtocol Contract:
- This contract inherits all LendingPool functionality with updated reward distribution and liquidity functionality.
- Users can deposit the blockchain's native token at any time in exchange for minted HLP tokens.
- HLP tokens can also be redeemed at any time for an equal amount of the blockchain's native token. HLP tokens are burned from the user upon redemption.
- When rewards are deposited into this contract, they are split between a LendingRewards contract used for HYPE holder rewards and another LendingRewards contract used for HLP holder rewards.
- The owner can update the proportions of this split at any time.
LendingPoolTokenCustodian Contract:
- This contract defines the "Hyperbolic Protocol" ERC-20 token where each token additionally represents a share in the HolderRewards contract for addresses not excluded from rewards.
- Users' shares in the HolderRewards contract will be adjusted as tokens are sent and received.
- 100 million tokens are minted upon deployment, where 10% is minted to the RewardsLocker contract, and the remaining 90% is minted to the owner.
- Any user may burn their own tokens at any time to reduce the total supply.
- HYPE tokens may not be bought or sold until the owner has set the launch time.
- Any address that buys HYPE tokens within 10 seconds of the launch time will be marked as a bot.
- Addresses marked as bots will not be able to send or receive tokens at any time.
- Any address may not have more than 1% of the token's total supply within 30 minutes of the launch time.
- When taxes are enabled, the tax percentage will fall between the minimum and maximum tax percentage, determined by the LendingPool balance to target market cap ratio.
- All taxes collected will be stored within this contract until the "swap threshold" is reached.
- The contract will perform an automated swap and process when the following conditions are met:
- Swapping is enabled.
- The owner has initialized a Uniswap liquidity position.
- The contract's HYPE token balance has reached the swap threshold.
- An LP percentage of the accumulated HYPE fees are used for a liquidity add, where half of the tokens are swapped for ETH, paired with the remaining half, and added as liquidity.
- Any remaining tokens and ETH from the liquidity add are transferred to the owner.
- The remaining portion of the accumulated HYPE fees are swapped for ETH and deposited into the LendingPool contract.
- The owner may create a Uniswap liquidity pool at a specified fee amount at any time.
- The owner may create a liquidity position in one of the liquidity pools with a specified number of the contract's tokens at any time, along with supplied ETH.
- The owner can manually execute a swap and process at any time if the swap threshold has been reached.
- The owner may toggle any address as an Automated Market Maker (AMM) at any time. AMMs are used to identify buys and sells, and are excluded from rewards.
- The owner can update the swap threshold and toggle swap and process functionality at any time.
- The project team should ensure that the swap threshold is limited in order to prevent frontrunning attacks.
- The owner can update the pool to market cap target at any time.
- The owner can disable taxes on buys, sells, or transfers at any time.
- The owner can set the minimum and maximum tax percentages to up to 20% each at any time. The team should exercise caution and ensure that the minimum tax is always less than or equal to the maximum tax.
- The owner can update the LP percentage at any time.
- The owner can update the LendingPool address at any time.
- The owner can exclude any address from rewards at any time.
- The owner can remove an address from the bot list at any time.
RewardsLocker Contract:
- This contract is used to distribute rewards to HYPE token holders.
- The contract also holds tokens that are deposited into the LendingPool contract.
- Each HYPE token represents 1 share in this contract. Users' balances are updated by the HyperbolicProtocol contract as tokens are transferred.
- Users' shares are not impacted if their HYPE tokens are deposited into the LendingPool contract.
- Users will earn a reward in ETH per share.
- Users' rewards will be distributed each time their share balance is updated.
- Users may manually claim their rewards at any time.
- Any address may deposit rewards into the contract.
- Depositing rewards will add to the accumulated reward per share for all users.
- Any ETH transferred to this contract are deposited as rewards while the contract holds any HYPE tokens. If the contract does not hold any HYPE, received ETH is sent to the contract's owner, intended to be the LendingPool address.
TwapUtils Contract:
- This contract is used to claim rewards in the associated LendingRewards contract and transfer them to this contract's owner.
- Any address can initiate a reward withdrawal to the owner at any time.
- Ownership can be transferred at any time.
- This contract is used to determine the current value of a token in a UniswapV3 liquidity pool.
- The price is determined using a Time-Weighted Average Price Oracle (TWAP).
- The price is currently calculated using a 5-minute interval.
- The owner may set the TWAP interval to any value at any time.
- The team should adjust the TWAP interval as needed to maintain price accuracy.
Audit Results
Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Jump/Storage Write | N/A | PASS |
Centralization of Control |
|
WARNING |
Compiler Issues | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Ether/Token Theft | N/A | PASS |
Flash Loans | N/A | PASS |
Front Running | N/A | PASS |
Improper Events | N/A | PASS |
Improper Authorization Scheme | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Logical Issues |
|
WARNING |
Oracle Issues | N/A | PASS |
Outdated Compiler Version | N/A | PASS |
Race Conditions | N/A | PASS |
Reentrancy | N/A | PASS |
Signature Issues | N/A | PASS |
Sybil Attack | N/A | PASS |
Unbounded Loops | N/A | PASS |
Unused Code | N/A | PASS |
Overall Contract Safety | WARNING |
Contract Source Summary and Visualizations
Name |
Address/Source Code |
Visualized |
LoanToken |
||
LendingPoolExtLP, LendingPool, & HLP |
||
HyperbolicProtocol |
||
LendingRewards |
||
RewardsLocker |
||
TwapUtils |
About SourceHat
SourceHat (formerly Solidity Finance - founded in 2020) has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1700+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!
Contact us today to get a free quote for a smart contract audit of your project!
What is a SourceHat Audit?
Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.
How Do I Interpret the Findings?
Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:
- High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
- Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
- Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
- Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.