Halborn Logo
Draft

Token Earn Contract - Halo


Prepared by:

Halborn Logo

HALBORN

Last Updated 01/03/2025

Date of Engagement by: December 20th, 2024 - December 27th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

12

Critical

0

High

0

Medium

1

Low

3

Informational

8


1. Introduction

Halo engaged Halborn to conduct a security assessment on their smart contracts beginning on December 20th, 2024 and ending on December 27th, 2024. The security assessment was scoped to the smart contracts provided to Halborn. Commit hashes and further details can be found in the Scope section of this report.

The Halo codebase in scope consist of 4 different smart contracts

    • The HGPBurn.sol contract is used to allow Halo Genesis Pass owners to burn their NFTs in batches

    • The HaloAirdrop.sol contract utilizes Merkle proofs to allow users to claim rewards

    • The HaloStakeVault.sol contract allows users to stake HALO tokens and in return receive rewards proportional to the duration and amount of tokens they have staked

    • The HaloSocialMining.sol contract utilizes Merkle proofs to allow users to claim rewards for different "sessions"

2. Assessment Summary

Halborn was provided 7 days for the engagement and assigned 1 full-time security engineer to review the security of the smart contracts in scope. The engineer is a blockchain and smart contract security expert with advanced smart contract hacking skills, and deep knowledge of multiple blockchain protocols.

The purpose of the assessment is to:

    • Identify potential security issues within the smart contracts.

    • Ensure that smart contract functionality operates as intended.

In summary, Halborn identified some improvements to reduce the likelihood and impact of risks. The main ones are the following:

    • Consider making the ACC_PRECISION constant variable in the HaloStakeVault.sol contract an immutable one, and setting it in the constructor.

    • Use a multisig wallet for the owner address.

    • Consider implementing a pausing mechanism in the HaloStakeVault.sol contract.

    • Add unit and E2E tests.

3. Test Approach and Methodology

Halborn performed a combination of manual review of the code and automated security testing to balance efficiency, timeliness, and accuracy in regard to the scope of this assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of smart contracts and can quickly identify items that do not follow security best practices.

The following phases and associated tools were used throughout the term of the assessment:

    • Research into architecture, purpose and use of the platform.

    • Smart contract manual code review and walkthrough to identify any logic issue.

    • Thorough assessment of safety and usage of critical Solidity variables and functions in scope that could lead to arithmetic related vulnerabilities.

    • Local testing with custom scripts (Foundry).

    • Fork testing against main networks (Foundry).

    • Static analysis of security for scoped contract, and imported functions(Slither).

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:
EXPLOITABILITY 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: halo-token-earn-contract
(b) Assessed Commit ID: 0573363
(c) Items in scope:
  • contracts/interfaces/EventsAndErrors.sol
  • contracts/interfaces/IHaloWalletGenesisPass.sol
  • contracts/HGPBurn.sol
↓ Expand ↓
Out-of-Scope:
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

1

Low

3

Informational

8

Security analysisRisk levelRemediation Date
HAL-08 - Decimal Precision Incompatibility in Reward Accrual with HALO TokensMediumPartially Solved - 01/02/2025
HAL-09 - Quorum Challenges with High Staking of HALO Governance TokensLowRisk Accepted - 01/02/2025
HAL-10 - Centralization riskLowSolved - 01/02/2025
HAL-11 - Missing pause mechanismLowRisk Accepted - 01/02/2025
HAL-02 - Custom Errors Should be UsedInformationalAcknowledged - 01/02/2025
HAL-07 - Owners can renounce ownershipInformationalAcknowledged - 01/02/2025
HAL-05 - Use of transfer()InformationalAcknowledged - 01/02/2025
HAL-12 - Insufficient test coverageInformationalFuture Release - 01/02/2025
HAL-01 - Floating pragmaInformationalAcknowledged - 01/02/2025
HAL-03 - Consider Using Named MappingsInformationalAcknowledged - 01/02/2025
HAL-04 - Unused importInformationalSolved - 01/02/2025
HAL-06 - Use of memory instead of calldata for an unmodified function argumentInformationalAcknowledged - 01/02/2025

7. Findings & Tech Details

7.1 (HAL-08) Decimal Precision Incompatibility in Reward Accrual with HALO Tokens

// Medium

Description

In the HaloStakeVault.sol contract, the stakeToken and rewardToken global variables are set when the contract is deployed. The ACC_PRECISION is a constant variable with the value of 1e18. If the rewardToken is, for example, USDC, after a certain amount of the stakeToken which will be HALO a token with 18 decimals, is staked rewards won't accrue. Let's consider the following example:

  • The rewardToken is USDC, a token with 6 decimals.

  • The rewardRatePerBlock is 10e6

  • The totalStaked is 10_000_001e18

When the updatePool() function is called, and only one block has passed, the following amount will be added to the accHaloPerShare - 10e6 * 1e18 / 10_000_001e18 = 0. The updatePool() function is permissionless. Thus, once a certain amount of HALO tokens are staked, no more rewards will be accrued, and the users who have staked their HALO tokens won't receive the rewards that they should receive.


BVSS
Recommendation

Consider making the ACC_PRECISION variable an immutable one, and setting it in the constructor.

Remediation

Partially Solved: The protocol team correctly followed the recommended fix, however it introduced another issue which may result in users who deposit small amounts of the stake token, to not receive any potential rewards if they withdraw too early. The protocol team has accepted this risk, and chosen to inform the users about it in its documentation.

Remediation Hash
References

7.2 (HAL-09) Quorum Challenges with High Staking of HALO Governance Tokens

// Low

Description

The HALO token is a governance token, and is the token which is staked in the HaloStakeVault.sol contract. The total supply of the token is 2_100_000_000e18, depending on the quorum requirements if a big portion of the token is staked, quorum won't be reached in the upcoming governance proposals, as the staked tokens don't delegate the voting rights to anyone, and the HALO tokens that are deposited in the HaloStakeVault.sol contract are not utilized in any way.

BVSS
Recommendation

Consider delegating the voting rights associated with the HALO tokens to the address provided as the to parameter in the stake() function.

Remediation

Risk Accepted: The protocol team has chosen not to add voting-related logic yet

7.3 (HAL-10) Centralization risk

// Low

Description

If the owner address is compromised, core functionality of the protocol will be affected. For example, in the HaloAirdrop.sol contract, a compromised owner can pause the contract indefinitely, or withdraw the tokens that are deposited and should be claimed by the users via the pause() and emergencyWithdraw() / emergencyWithdrawERC20() functions respectively.

In the HaloSocialMining.sol contract, a compromised owner can set a new merkle root via the setSessionMerkleRoot() function, and drain all of the rewards, or simply call the emergencyWithdraw() / emergencyWithdrawERC20() in order to drain the tokens within the contract.

In the HaloStakeVault.sol contract, a compromised owner can either increase drastically the reward per block, or make it 0 via the updateRewardRate() function, so that stakers don't receive any rewards. It is also possible to set a rewardVault address that doesn't have any rewards in it via the updateRewardVault() function.

The rewardVault contract / EOA is a single point of failure, if the reward tokens contained within it are drained, the users who staked their tokens won't receive any rewards.

BVSS
Recommendation

Consider using multisig wallet address as the owner address.


Remediation

Solved: The protocol team will set the owner to a 2/3 multiSig wallet.

Remediation Hash
References

7.4 (HAL-11) Missing pause mechanism

// Low

Description

The HaloStakeVault.sol contract does not include a pause mechanism that would allow the contract owner to temporarily disable certain functions in the contract. A pause mechanism is a critical feature that can be used to prevent potential issues in the event of a bug or vulnerability being discovered in the contract. It can also be used to prevent malicious actors from exploiting the contract while a fix is being implemented.

BVSS
Recommendation

Implement a pause mechanism in the HaloStakeVault.sol contract that allows the contract owner to disable certain functions in the contract in case of an emergency or bug discovery.

Remediation

Risk Accepted: The protocol team has accepted the risk and chosen not to implement an additional pausing mechanism.

References

7.5 (HAL-02) Custom Errors Should be Used

// Informational

Description

In Solidity smart contract development, replacing hard-coded revert message strings with the Error() syntax is an optimization strategy that can significantly reduce gas costs. Hard-coded strings, stored on the blockchain, increase the size and cost of deploying and executing contracts.

The Error() syntax allows for the definition of reusable, parameterized custom errors, leading to a more efficient use of storage and reduced gas consumption. This approach not only optimizes gas usage during deployment and interaction with the contract but also enhances code maintainability and readability by providing clearer, context-specific error information.

BVSS
Recommendation

It is recommended to replace hard-coded revert strings in require statements for custom errors, which can be done following the logic below.

1. Standard require statement (to be replaced):

require(condition, "Condition not met");

2. Declare the error definition to state

error ConditionNotMet();

3. As currently is not possible to use custom errors in combination with require statements, the standard syntax is:

if (!condition) revert ConditionNotMet();

More information about this topic in [Official Solidity Documentation](https://docs.soliditylang.org/en/v0.8.24/control-structures.html#panic-via-assert-and-error-via-require).

Remediation

Acknowledged: The protocol team has acknowledged the issue, and chosen not to make changes.

References

7.6 (HAL-07) Owners can renounce ownership

// Informational

Description

The protocol allows the current owner to renounce ownership, which will disable important functionality of the protocol such as pausing and unpausing the claiming and unlocking of user rewards within the HaloAirdrop.sol contract, and updating the rewards per block in the HaloStakeVault.sol contract.

BVSS
Recommendation

Consider disallowing the owner address to be set to address(0).

Remediation

Acknowledged: The protocol team has acknowledged the issue, and chosen not to make changes.

References

7.7 (HAL-05) Use of transfer()

// Informational

Description

In the HaloSocialMining.sol and HaloAirdrop.sol contracts when an emergency withdraw of the native tokens is performed, the solidity build in transfer() function is used to handle the native token transfer, it does this by forwarding a fixed amount of 2300 gas. This is dangerous for two reasons:

  1. Gas costs of EVM instructions may change significantly during hard forks, which may previously assumed fixed gas costs. EIP 1884 as an example, broke several existing smart contracts due to a cost increase of the SLOAD instruction.

  2. If the recipient is a contract or a multisig safe, with a receive/fallback function which requires >2300 gas, e.g safes that execute extra logic in the receive/fallback function, the transfer function will always fail for them due to out of gas errors.

BVSS
Recommendation

Consider using the call() method for transferring funds, as it is more flexible and handles the increase in gas cost associated with the transfer.

Remediation

Acknowledged: The protocol team has acknowledged the issue, and chosen not to make changes.

References

7.8 (HAL-12) Insufficient test coverage

// Informational

Description

The project lacks unit and E2E tests. It is always a good practice to implement unit tests and E2E tests, as it helps discovering vulnerabilities in the early stages of development, and later on it helps with maintaining and updating the code base.

Score
Recommendation

Consider adding unit and E2E tests to the project.

Remediation

Future Release: The protocol has informed us that there are tests, however they are not considered opensource as of the date of the audit.

7.9 (HAL-01) Floating pragma

// Informational

Description

All contracts in scope currently use floating pragma versions ^0.8.27 which means that the code can be compiled by any compiler version that is greater than or equal to 0.8.27, and less than 0.9.0.

However, it is recommended that contracts should be deployed with the same compiler version and flags used during development and testing. Locking the pragma helps to ensure that contracts do not accidentally get deployed using another pragma. For example, an outdated pragma version might introduce bugs that affect the contract system negatively.

In this aspect, it is crucial to select the appropriate EVM version when it's intended to deploy the contracts on networks other than the Ethereum mainnet, which may not support these opcodes. Failure to do so could lead to unsuccessful contract deployments or transaction execution issues.

Score
Recommendation

Lock the pragma version to the same version used during development and testing.

Additionally, make sure to specify the target EVM version when using Solidity versions from 0.8.20 and above if deploying to chains that may not support newly introduced opcodes. Additionally, it is crucial to stay informed about the opcode support of different chains to ensure smooth deployment and compatibility.

Remediation

Acknowledged: The protocol team has acknowledged the issue, and chosen not to make changes.

References

7.10 (HAL-03) Consider Using Named Mappings

// Informational

Description

The project is using a Solidity version greater than 0.8.18, which supports named mappings. Using named mappings can improve the readability and maintainability of the code by making the purpose of each mapping clearer. This practice helps developers and auditors understand the mappings' intent more easily.

Score
Recommendation

Consider refactoring the mappings to use named arguments, which will enhance code readability and make the purpose of each mapping more explicit.

For example, on contracts/HaloStakeVault.sol, instead of declaring:

mapping(address => uint256) public rewardsToClaim;

It could be declared as:

mapping(address userAddress => uint256 amount) public rewardsToClaim;

Remediation

Acknowledged: The protocol team has acknowledged the issue, and chosen not to make changes.

References

7.11 (HAL-04) Unused import

// Informational

Description

The HGPBurn.sol contract imports the import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; however it doesn't use it.


Score
Recommendation

Consider removing the unused import, the import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; is imported in the IHaloWalletGenesisPass.sol.


Remediation

Solved: The protocol has removed the unused import.

Remediation Hash
References

7.12 (HAL-06) Use of memory instead of calldata for an unmodified function argument

// Informational

Description

In the HaloAirdop.sol contract in the isValid() function, the memory keyword is used for a parameter which is not modified instead of the calldata keyword.

Score
Recommendation

Consider using the calldata keyword instead of the memory for function arguments which are not modified.

Remediation

Acknowledged: The protocol team has acknowledged the issue, and chosen not to make changes.

References

8. Automated Testing

Static Analysis Report

Description

Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contracts in scope. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified the smart contracts in the repository and was able to compile them correctly into their abis and binary format, Slither was run against the contract. This tool can statically verify mathematical relationships between Solidity variables to detect invalid or inconsistent usage of the contracts' APIs across the entire code-base.

The security team assessed all findings identified by the Slither software, however, most of the findings are not included in the below results for the sake of report readability.


Output

The findings obtained as a result of the Slither scan were reviewed, and the majority were not included in the report because they were determined as false positives.

Slither results


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.