Halborn Logo

Finance Contracts - Bonzo


Prepared by:

Halborn Logo

HALBORN

Last Updated 09/05/2024

Date of Engagement by: June 24th, 2024 - July 5th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

11

Critical

4

High

0

Medium

1

Low

1

Informational

5


1. Introduction

Bonzo engaged our security analysis team to conduct a comprehensive security audit of their smart contract ecosystem. The primary aim was to meticulously assess the security architecture of the smart contracts to pinpoint vulnerabilities, evaluate existing security protocols, and offer actionable insights to bolster security and operational efficacy of their smart contract framework. Our assessment was strictly confined to the smart contracts provided, ensuring a focused and exhaustive analysis of their security features.

2. Assessment Summary

Our engagement with Bonzo spanned a 1.5-week period, during which we dedicated one full-time security engineer equipped with extensive experience in blockchain security, advanced penetration testing capabilities, and profound knowledge of various blockchain protocols. The objectives of this assessment were to:

- Verify the correct functionality of smart contract operations.

- Identify potential security vulnerabilities within the smart contracts.

- Provide recommendations to enhance the security and efficiency of the smart contracts.

3. Test Approach and Methodology

Our testing strategy employed a blend of manual and automated techniques to ensure a thorough evaluation. While manual testing was pivotal for uncovering logical and implementation flaws, automated testing offered broad code coverage and rapid identification of common vulnerabilities. The testing process included:

- A detailed examination of the smart contracts' architecture and intended functionality.

- Comprehensive manual code reviews and walkthroughs.

- Functional and connectivity analysis utilizing tools like Solgraph.

- Customized script-based manual testing and testnet deployment using Foundry.

This executive summary encapsulates the pivotal findings and recommendations from our security assessment of Bonzo's smart contract ecosystem. By addressing the identified issues and implementing the recommended fixes, Bonzo can significantly boost the security, reliability, and trustworthiness of its smart contract platform.

4. RISK METHODOLOGY

Every vulnerability and issue observed by Halborn is ranked based on two sets of Metrics and a Severity Coefficient. This system is inspired by the industry standard Common Vulnerability Scoring System.
The two Metric sets are: Exploitability and Impact. Exploitability captures the ease and technical means by which vulnerabilities can be exploited and Impact describes the consequences of a successful exploit.
The Severity Coefficients is designed to further refine the accuracy of the ranking with two factors: Reversibility and Scope. These capture the impact of the vulnerability on the environment as well as the number of users and smart contracts affected.
The final score is a value between 0-10 rounded up to 1 decimal place and 10 corresponding to the highest security risk. This provides an objective and accurate rating of the severity of security vulnerabilities in smart contracts.
The system is designed to assist in identifying and prioritizing vulnerabilities based on their level of risk to address the most critical issues in a timely manner.

4.1 EXPLOITABILITY

Attack Origin (AO):
Captures whether the attack requires compromising a specific account.
Attack Cost (AC):
Captures the cost of exploiting the vulnerability incurred by the attacker relative to sending a single transaction on the relevant blockchain. Includes but is not limited to financial and computational cost.
Attack Complexity (AX):
Describes the conditions beyond the attacker’s control that must exist in order to exploit the vulnerability. Includes but is not limited to macro situation, available third-party liquidity and regulatory challenges.
Metrics:
EXPLOITABILIY METRIC (mem_e)METRIC VALUENUMERICAL VALUE
Attack Origin (AO)Arbitrary (AO:A)
Specific (AO:S)
1
0.2
Attack Cost (AC)Low (AC:L)
Medium (AC:M)
High (AC:H)
1
0.67
0.33
Attack Complexity (AX)Low (AX:L)
Medium (AX:M)
High (AX:H)
1
0.67
0.33
Exploitability EE is calculated using the following formula:

E=meE = \prod m_e

4.2 IMPACT

Confidentiality (C):
Measures the impact to the confidentiality of the information resources managed by the contract due to a successfully exploited vulnerability. Confidentiality refers to limiting access to authorized users only.
Integrity (I):
Measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of data stored and/or processed on-chain. Integrity impact directly affecting Deposit or Yield records is excluded.
Availability (A):
Measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability. This metric refers to smart contract features and functionality, not state. Availability impact directly affecting Deposit or Yield is excluded.
Deposit (D):
Measures the impact to the deposits made to the contract by either users or owners.
Yield (Y):
Measures the impact to the yield generated by the contract for either users or owners.
Metrics:
IMPACT METRIC (mIm_I)METRIC VALUENUMERICAL VALUE
Confidentiality (C)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Integrity (I)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Availability (A)None (A:N)
Low (A:L)
Medium (A:M)
High (A:H)
Critical (A:C)
0
0.25
0.5
0.75
1
Deposit (D)None (D:N)
Low (D:L)
Medium (D:M)
High (D:H)
Critical (D:C)
0
0.25
0.5
0.75
1
Yield (Y)None (Y:N)
Low (Y:L)
Medium (Y:M)
High (Y:H)
Critical (Y:C)
0
0.25
0.5
0.75
1
Impact II is calculated using the following formula:

I=max(mI)+mImax(mI)4I = max(m_I) + \frac{\sum{m_I} - max(m_I)}{4}

4.3 SEVERITY COEFFICIENT

Reversibility (R):
Describes the share of the exploited vulnerability effects that can be reversed. For upgradeable contracts, assume the contract private key is available.
Scope (S):
Captures whether a vulnerability in one vulnerable contract impacts resources in other contracts.
Metrics:
SEVERITY COEFFICIENT (CC)COEFFICIENT VALUENUMERICAL VALUE
Reversibility (rr)None (R:N)
Partial (R:P)
Full (R:F)
1
0.5
0.25
Scope (ss)Changed (S:C)
Unchanged (S:U)
1.25
1
Severity Coefficient CC is obtained by the following product:

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

S=min(10,EIC10)S = min(10, EIC * 10)

The score is rounded up to 1 decimal places.
SeverityScore Value Range
Critical9 - 10
High7 - 8.9
Medium4.5 - 6.9
Low2 - 4.4
Informational0 - 1.9

5. SCOPE

Files and Repository
(a) Repository: bonzo-finance-contracts
(b) Assessed Commit ID: 3b3de0b
(c) Items in scope:
  • contracts/misc/WHBAR/HederaTokenService.sol
  • contracts/misc/WHBAR/Bits.sol
  • contracts/misc/WHBAR/SafeHederaTokenService.sol
↓ Expand ↓
Out-of-Scope:
Remediation Commit ID:
  • 9f29c68
  • 660ee46
  • 6103d6c
  • 78c92be
  • fbfaee1
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

4

High

0

Medium

1

Low

1

Informational

5

Security analysisRisk levelRemediation Date
Incorrect asset handling during withdrawCriticalSolved - 07/23/2024
Incorrect asset handling during executeBorrowCriticalSolved - 07/23/2024
Stable Rate Mode Critical BugCriticalSolved - 07/24/2024
Price Inflation in ATokenCriticalSolved - 07/23/2024
Unsafe Modification of Decimals in LendingPoolConfiguratorMediumSolved - 07/23/2024
Lack of Two-step Ownership Transfer and Unsafe renounceOwnershipLowSolved - 07/23/2024
Suboptimal Handling of WHBAR Variables and DeploymentInformationalAcknowledged
Duplicate HederaTokenService ContractsInformationalSolved - 07/23/2024
Inconsistent return handling in redirectForTokenInformationalSolved - 07/23/2024
Suboptimal Contract Design for getDecimalsInformationalSolved - 07/23/2024
Suboptimal Token Association and Transfer Mechanism in ATokenInformationalAcknowledged

7. Findings & Tech Details

7.1 Incorrect asset handling during withdraw

// Critical

Description

During the withdraw function execution in the LendingPool contract, there is an issue with the handling of the whbar asset. The burn function of the AToken contract transfers the _underlyingAsset to the receiverOfUnderlying, which corresponds to the to parameter in the withdraw function. If the asset is the wrapped HBAR ( whbar ), a withdraw is performed on the msg.sender, sending the unwrapped HBAR to the to address. This design assumes that msg.sender and to are the same, which might not be the case. Consequently, this can lead to a scenario where the caller ( msg.sender ) pays for the whbar withdrawal, resulting in double payment for the to address if msg.sender is different from to.

Additionally, there is no allowance mechanism in place for this operation. The withdraw function may fail if msg.sender has not approved the LendingPool for the amountToWithdraw, potentially leading to the use of unrelated allowances.

BVSS
Recommendation

To resolve this issue, modify the withdraw logic for whbar assets. Always transfer the funds to address(this) or a controlled vault address with the necessary approvals for the whbarContract. Then, unwrap the tokens via IWHBAR(_whbarContract).withdraw to the to address.

// Note - we are withdrawing the user's _whbarToken tokens and send them HBAR
if (asset == address(_whbarToken)) {
    IAToken(aToken).burn(msg.sender, address(this), amountToWithdraw, reserve.liquidityIndex);
    // Note the function, it is a direct `withdraw` without a "from". (approving first could be required)
    IWHBAR(_whbarContract).withdraw(to, amountToWithdraw);
} else {
    IAToken(aToken).burn(msg.sender, to, amountToWithdraw, reserve.liquidityIndex);
}

Ensure the controlled vault address has the necessary permissions to handle whbar withdrawals. This approach prevents double payment scenarios and ensures proper asset handling during the withdrawal process.


Remediation

SOLVED: The suggestion was applied by the Bonzo team.

Remediation Hash
9f29c6812e7f962b5f06513c91d7d17123e0650d

7.2 Incorrect asset handling during executeBorrow

// Critical

Description

During the _executeBorrow function in the LendingPool contract, there is a potential issue when releaseUnderlying is set to true. If the underlying asset is the wrapped HBAR ( _whbarToken ), the unwrapping process should be carefully managed. Currently, the code unwraps and sends the HBAR to vars.onBehalfOf, which can lead to incorrect handling and unnecessary complexity.

The unwrapping should only occur when releaseUnderlying is true, and the unwrapped HBAR should be sent to vars.user, who is the one receiving the underlying asset through transferUnderlyingTo. This ensures that the correct user receives the unwrapped HBAR and avoids any confusion or double payments.

BVSS
Recommendation

Modify the _executeBorrow function to handle the unwrapping of _whbarToken properly. Ensure that the unwrapping only occurs when releaseUnderlying is true and that the unwrapped HBAR is sent to vars.user.

if (vars.releaseUnderlying) {
    if (vars.asset == address(_whbarToken)) {
        IAToken(vars.aTokenAddress).transferUnderlyingTo(address(this), vars.amount);
        IWHBAR(_whbarContract).withdraw(vars.user, vars.amount);
    } else {
        IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
    }
}

By implementing this change, the borrowing process will correctly handle the unwrapping of _whbarToken, ensuring that the appropriate user receives the unwrapped HBAR and preventing any potential issues related to asset handling during borrowing.

Remediation

SOLVED: The suggestion was applied by the Bonzo team.

Remediation Hash
660ee467156ad3078e0d61e07e347a920eb1d621

7.3 Stable Rate Mode Critical Bug

// Critical

Description

A critical bug related to the stable borrow rate was reported on November 4, 2023. This bug affects certain assets due to their configuration and poses a significant risk. Given the nature of the bug and the progressive deprecation of the stable rate in Aave v2 and v3, it was decided to fully deprecate the stable rate mode. The fix involved halting new borrowings in stable mode, as well as stopping rebalancing and swapping from variable to stable. Although we are confident that there is no further vector for the bug, the current situation creates significant technical overhead and complicates security evaluations. Furthermore, disclosing the attack vector is not safe until all user stable debt positions are closed.

https://governance.aave.com/t/bgd-full-deprecation-of-stable-rate/16473

BVSS
Recommendation

To mitigate this issue, it is recommended to update the swapBorrowRateMode function in the Aave Pool smart contract on both Aave v2 and v3 to make the swap from stable to variable rate permission-less. This will allow any address to trigger the rate swap, enabling the Aave DAO to automate the process of moving all current stable debt positions to variable without penalizing users. Once all positions are in variable mode, the stable rate mode can be fully deprecated.

Remediation

SOLVED: The suggestion was applied by the Bonzo team.

Remediation Hash
660ee467156ad3078e0d61e07e347a920eb1d621

7.4 Price Inflation in AToken

// Critical

Description

The AToken contract is susceptible to price inflation due to liquidity index manipulation and rounding issues in the mint and burn functions. This vulnerability can be exploited to extract profit, particularly in scenarios where the initial pool balance is not properly initialized. While Aave v2 deployment scripts typically initialize the pool with an initial balance to prevent the First Deposit Bug, this is not always guaranteed. Similar issues have been observed in other protocols, such as Compound and Radiant Lending Pool.

BVSS
Recommendation

1. Initialize Pool with Initial Balance:

Ensure that the pool is initialized with a small balance to prevent the First Deposit Bug. This can be achieved by minting a small number of tokens to an unused address (e.g., the zero address).

Reference:

- Compound Initial Mint Fix

- Uniswap V2 Mint Fix

Code Snippet:

    if (totalSupply == 0) {
        totalSupply = 1000;
        accountTokens[address(0)] = 1000;
        mintTokens -= 1000;
    }

2. Ensure Non-zero Shares Minted:

When minting tokens, ensure that the number of shares minted is non-zero to prevent rounding issues.

Code Snippet:

   require(_shares != 0, "zero shares minted");

3. Create Periphery Contract for Initialization:

Create a periphery contract that contains a wrapper function to atomically call initialize and deposit. This ensures that the initialization and first deposit are handled in a single transaction.

By implementing these changes, the risk of price inflation and associated exploits can be significantly reduced, enhancing the overall security and stability of the AToken contract.

References:

- Hopelend Incident Analysis

- Radiant Lending Pool Hack Analysis

- Compound Initial Mint Fix

- Uniswap V2 Mint Fix


Remediation

SOLVED: A periphery contract was added, named LendingPoolPeriphery that initializes the pool as soon as it is deployed.

Remediation Hash
6103d6c38751db070d7ae44ae01794548db2adb9

7.5 Unsafe Modification of Decimals in LendingPoolConfigurator

// Medium

Description

In the LendingPoolConfigurator contract, the setDecimals function allows changing the number of decimals for a reserve asset. This can lead to critical issues if the new decimals do not match the underlying asset's actual decimals. The core functions getAmountInEth and _executeBorrow assume the decimals correspond to the underlying asset, which impacts calculations involving collateral and borrowing.

Assume:

  • Original decimals of the underlying asset: D1

  • New decimals set via setDecimals: D2

  • Amount of asset held by a user: A

  • Price of the asset in ETH: P

Value in ETH with original decimals:

V1 = (A*P)/10^{D1}

Value in ETH with new decimals:

V2 = (A*P)/10^{D2}

Impact:

  • If D2 > D1, V2 < V1: The collateral value in ETH is underestimated, leading to potential under-collateralization and allowing excessive borrowing.

  • If D2 < D1, V2 > V1: The collateral value in ETH is overestimated, restricting borrowing more than necessary.

Changing the decimals without ensuring they match the underlying asset's decimals can cause:

  • Underestimation or overestimation of collateral value.

  • Discrepancies in borrowing calculations.

  • Potential liquidation or over-leverage scenarios for users.

This demonstrates the critical issues that arise from mismatched decimals, emphasizing the need for consistent handling of asset decimals across the smart contract system.

BVSS
Recommendation

1. Remove the setDecimals Functionality: Consider removing the ability to change the decimals of a reserve asset to maintain consistency and avoid critical issues related to collateral and borrowing calculations.

2. Restrict Decimals Change: If retaining the functionality, restrict changes to only when there are no supplies or borrows in the pool. Ensure that the new decimals match the underlying asset's actual decimals by performing a validation check before applying the change.

These measures will help maintain the integrity of the system and prevent discrepancies in collateral and borrowing values.

Remediation

SOLVED: The setDecimals function was removed.

Remediation Hash
9f29c6812e7f962b5f06513c91d7d17123e0650d

7.6 Lack of Two-step Ownership Transfer and Unsafe renounceOwnership

// Low

Description

In the Ownable contracts, such as SupraOracle and others, the current ownership transfer mechanism might be insecure. A two-step ownership transfer process is recommended to prevent accidental transfer or loss of contract control. Additionally, the renounceOwnership function should be overridden to revert the transaction, avoiding the possibility of leaving the contract in an unusable state without an owner.

BVSS
Recommendation

Implement a two-step ownership transfer process by introducing a pendingOwner state. Also, override the renounceOwnership function to revert the transaction.

Remediation

SOLVED: The issue was solved.

Remediation Hash
78c92be496b8d636d034059b4037c48153a25ffc

7.7 Suboptimal Handling of WHBAR Variables and Deployment

// Informational

Description

The LendingPool contract currently uses two storage variables, _whbarContract and _whbarToken, to manage wrapped HBAR (WHBAR). These variables could be optimized by storing the WHBAR contract address as an immutable variable and using address(0) to represent native HBAR. This would reduce storage usage and simplify the code by eliminating the need for redundant variables.

Additionally, the current implementation uses testnet addresses for _whbarContract and _whbarToken. Using immutable variables set during contract deployment can help avoid errors and make the contract more secure and efficient.

BVSS
Recommendation

1. Use an immutable variable for _whbarContract.

2. Use address(0) to represent the WHBAR token, removing the need for _whbarToken.

3. Update the contract deployment process to set the immutable _whbarContract variable.

This approach enhances the contract's efficiency and security by leveraging Solidity's immutable variables and simplifying the handling of wrapped HBAR.


Remediation

ACKNOWLEDGED: Ignoring, because ValidationLogic.sol functions need to have an actual reserve for the asset address

7.8 Duplicate HederaTokenService Contracts

// Informational

Description

There are multiple instances of the HederaTokenService contract within the codebase, one in the misc directory and the other in the tokenWrapper directory. These contracts contain duplicated functions such as createFungibleToken, mintToken, burnToken, associateTokens, and transferToken. Additionally, the misc directory contains a unique function, transferTokenRouter, which is essentially the same as transferToken but uses delegatecall.

Moreover, the HederaResponseCodes contract is duplicated under both the misc/HBAR and tokenWrapper directories. The one in the misc directory only defines the UNKNOWN and SUCCESS error codes, while the tokenWrapper directory one defines 330 errors.

BVSS
Recommendation

Consider merging the functionalities of the HederaTokenService contracts into a single contract that encompasses all required functionalities. If certain functionalities are not being used, consider removing them to reduce complexity and potential security risks.

For HederaResponseCodes, consolidate the error codes into a single contract to avoid redundancy and potential inconsistencies. Use the more comprehensive set of error codes defined in the tokenWrapper directory.

Remediation

SOLVED: The issue was solved.

Remediation Hash
fbfaee199a8cc514b28ea6046be9f8273234f05a

7.9 Inconsistent return handling in redirectForToken

// Informational

Description

The redirectForToken function in the HederaTokenService contract has inconsistent return handling between its original version and the version in scope. The original version returns (HederaResponseCodes.SUCCESS, result), while the in-scope version decodes the result as (int32, bytes). This inconsistency can lead to unexpected behavior and potential security vulnerabilities due to different interpretations of the return values.

BVSS
Recommendation

Standardize the return handling of the redirectForToken function to ensure consistent behavior. Update the in-scope version to match the original version's return format if that is the intended standard. Ensure thorough testing to verify that all functions correctly handle the return values.


Remediation

SOLVED: The issue was solved by removing the unused HederaTokenService.

Remediation Hash
fbfaee199a8cc514b28ea6046be9f8273234f05a

7.10 Suboptimal Contract Design for getDecimals

// Informational

Description

The getDecimals function, which retrieves the decimal configuration for a given asset, is currently implemented in the LendingPoolConfigurator contract. This design requires unnecessary contract interaction and complicates the retrieval process. Implementing this function directly in the LendingPool contract would streamline the process, making it more efficient and intuitive.

Score
Impact:
Likelihood:
Recommendation

Move the getDecimals function to the LendingPool contract to enhance the design and improve efficiency.

// In the LendingPool contract
function getDecimals(address asset) external view returns (uint8) {
    DataTypes.ReserveData storage reserve = _reserves[asset];
    return reserve.configuration.getDecimals();
}

This change ensures that the decimal information for a given asset can be accessed directly from the LendingPool contract, simplifying the codebase and reducing the need for cross-contract calls.


Remediation

SOLVED: The issue was solved.

Remediation Hash
fbfaee199a8cc514b28ea6046be9f8273234f05a

7.11 Suboptimal Token Association and Transfer Mechanism in AToken

// Informational

Description

The current implementation of the AToken contract does not leverage the Hedera Token Service (HTS) for token transfers, which can lead to inefficiencies and additional complexity in the code. By extending from the IHRC as specified in HIP-719, the AToken can use the HTS transfer mechanism. This approach allows users to associate themselves with the AToken, enabling more efficient and secure transfers, including mint and burn operations.

Users will need to call the LendingPool's AToken association function before receiving tokens. This change requires comprehensive testing and validation to ensure all liquidity rate updates and AToken balances are correctly handled.

Score
Impact:
Likelihood:
Recommendation

Extend the AToken contract from IHRC as specified in HIP-719. Update all internal transfers, including mint and burn, to use the HTS transfer mechanism. Ensure that users associate themselves with the AToken before receiving tokens. All transfers should use the "safe" variant to check for Hedera success codes.


Remediation

ACKNOWLEDGED: Ignoring it because we have taken a conscious business decision NOT to make the aTokens as HTS tokens in order to avoid their trading on DEXes.

8. Automated Testing

ERC20Wrapper

# Check ERC20Wrapper

## Check functions
[✓] totalSupply() is present
	[✓] totalSupply() -> (uint256) (correct return type)
	[✓] totalSupply() is view
[✓] balanceOf(address) is present
	[✓] balanceOf(address) -> (uint256) (correct return type)
	[✓] balanceOf(address) is view
[✓] transfer(address,uint256) is present
	[✓] transfer(address,uint256) -> (bool) (correct return type)
	[✓] Transfer(address,address,uint256) is emitted
[✓] transferFrom(address,address,uint256) is present
	[✓] transferFrom(address,address,uint256) -> (bool) (correct return type)
	[✓] Transfer(address,address,uint256) is emitted
[✓] approve(address,uint256) is present
	[✓] approve(address,uint256) -> (bool) (correct return type)
	[✓] Approval(address,address,uint256) is emitted
[✓] allowance(address,address) is present
	[✓] allowance(address,address) -> (uint256) (correct return type)
	[✓] allowance(address,address) is view
[✓] name() is present
	[✓] name() -> (string) (correct return type)
	[✓] name() is view
[✓] symbol() is present
	[✓] symbol() -> (string) (correct return type)
	[✓] symbol() is view
[✓] decimals() is present
	[✓] decimals() -> (uint8) (correct return type)
	[✓] decimals() is view

## Check events
[✓] Transfer(address,address,uint256) is present
	[✓] parameter 0 is indexed
	[✓] parameter 1 is indexed
[✓] Approval(address,address,uint256) is present
	[✓] parameter 0 is indexed
	[✓] parameter 1 is indexed
	[ ] ERC20Wrapper is not protected for the ERC20 approval race condition

Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.

© Halborn 2024. All rights reserved.