Halborn Logo
Draft

icon

SuperHedge v1 Core - SuperHedge


Prepared by:

Halborn Logo

HALBORN

Last Updated 12/03/2024

Date of Engagement by: November 5th, 2024 - November 8th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

31

Critical

3

High

0

Medium

1

Low

8

Informational

19


Table of Contents

1. Introduction

SuperHedge Finance engaged Halborn to conduct a security assessment of their v1 Core contracts beginning on November 5th and ending on November 8th. The security assessment was scoped to the smart contract provided in the GitHub repository. Commit hash and further details can be found in the Scope section of this report.

2. Assessment Summary

Halborn was provided 3 days for the engagement and assigned one full-time security engineer to review the security of the smart contract in scope. The engineer is blockchain and smart contract security expert with advanced penetration testing and 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 contract.

    • Ensure that smart contract functionality operates as intended.


In summary, Halborn identified some improvements to reduce the likelihood and impact of risks, which were mostly addressed by the SuperHedge Finance team. The main ones were the following: 

    • Implement a proper access control to restrict the burn() function to authorized accounts.

    • Replace the array-based user-tracking mechanism with a more efficient data structure or even remove it to prevent gas limit issues.

    • Unify balance-tracking logic by consolidating mappings and ensuring all functions consistently update user data.

    • Implement the missing updateName() function in the SHProduct contract or remove the broken setProductName() function entirely to avoid failed operations.

3. Test Approach and Methodology

Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, 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 the code and can quickly identify items that do not follow the security best practices. The following phases and associated tools were used during the assessment:

    • Research into architecture and purpose.

    • Smart contract manual code review and walkthrough.

    • Graphing out functionality and contract logic/connectivity/functions (solgraph).

    • Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic related vulnerability classes.

    • Manual testing by custom scripts.

    • 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: v1-core
(b) Assessed Commit ID: 3c822bc
(c) Items in scope:
  • contracts/interfaces/IERC20Token.sol
  • contracts/interfaces/ISHFactory.sol
  • contracts/interfaces/ISHProduct.sol
↓ Expand ↓
Out-of-Scope: Third party dependencies and economic attacks.
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

3

High

0

Medium

1

Low

8

Informational

19

Security analysisRisk levelRemediation Date
HAL-06 - Anyone Can Burn SHToken From Other UsersCriticalSolved - 11/12/2024
HAL-07 - Denial of Service in SHToken TransfersCriticalSolved - 11/13/2024
HAL-09 - Dual Mapping Flaw Causes Inaccurate Accounting and User List in SHTokenCriticalSolved - 11/13/2024
HAL-27 - Broken setProductName() Due to Missing ImplementationMediumSolved - 11/14/2024
HAL-29 - Incompatibilities With fee-on-transfer TokensLowSolved - 12/02/2024
HAL-17 - Lack of Tests and Incomplete NatSpec DocumentationLowSolved - 12/02/2024
HAL-25 - Unsafe ERC20 OperationsLowFuture Release - 11/29/2024
HAL-30 - Division by Zero Not PreventedLowSolved - 11/15/2024
HAL-15 - Limited Role Management and Unused DEFAULT_ADMIN_ROLELowSolved - 11/12/2024
HAL-04 - Missing Input ValidationLowSolved - 11/18/2024
HAL-05 - Single-step Ownership Transfer ProcessLowSolved - 11/19/2024
HAL-31 - Potential Rounding Errors in fundAccept()LowSolved - 11/13/2024
HAL-14 - Owner Can Renounce OwnershipInformationalSolved - 12/02/2024
HAL-12 - Missing Interface InheritanceInformationalSolved - 11/27/2024
HAL-11 - Use of Revert Strings Instead of Custom ErrorInformationalAcknowledged - 11/29/2024
HAL-21 - Cache Array Length Outside of LoopInformationalSolved - 11/27/2024
HAL-01 - Multiple Unlocked Solidity Versions in UseInformationalSolved - 11/27/2024
HAL-02 - Use of Unlicensed Smart ContractsInformationalSolved - 11/26/2024
HAL-03 - Lack of Event EmissionInformationalSolved - 11/27/2024
HAL-08 - Redundant Use of SafeMathInformationalSolved - 11/26/2024
HAL-10 - Style Guide OptimizationsInformationalSolved - 11/27/2024
HAL-13 - Redundant CodeInformationalSolved - 11/26/2024
HAL-16 - Commented Out CodeInformationalSolved - 11/26/2024
HAL-19 - Incorrect and Ambiguous Error MessagesInformationalSolved - 11/27/2024
HAL-20 - Redundant Boolean Equality ChecksInformationalSolved - 11/27/2024
HAL-22 - Consider Using Named MappingsInformationalSolved - 11/27/2024
HAL-23 - Redundant Use of block.timestamp in Event ParametersInformationalSolved - 11/27/2024
HAL-24 - Unused Imports and LibraryInformationalSolved - 11/26/2024
HAL-26 - Magic Numbers and Lack of Scientific NotationInformationalSolved - 11/27/2024
HAL-28 - Incorrect Order of Modifiers: nonReentrant Should Precede All Other ModifiersInformationalSolved - 11/27/2024
HAL-32 - Checks-Effects-Interactions Pattern Not FollowedInformationalSolved - 11/27/2024

7. Findings & Tech Details

7.1 (HAL-06) Anyone Can Burn SHToken From Other Users

// Critical

Description

The burn() function in the SHToken contract lacks proper access control, allowing any external user to call it and burn tokens from any account. This vulnerability can be exploited by malicious actors to arbitrarily reduce the token balance of any user without their consent. This flaw poses a significant risk to the token's integrity, potentially leading to unauthorized token destruction and financial loss for users.


See the code of the unprotected burn() function:

function burn(address account, uint256 amount) external {
    require(amount > 0, "Amount must be greater than zero");
    require(balanceOf(account) >= amount, "Insufficient balance");
    _burn(account, amount);
    _updateUserBalance(account, amount, false);
}
Proof of Concept

The following Foundry test demonstrates this vulnerability:

function test_UnauthorizedBurn() external {
    // Step 1: Verify initial balance of ALICE is zero.
    assertEq(shToken.balanceOf(ALICE), 0);

    // Step 2: Mint tokens to ALICE using the authorized token owner.
    vm.prank(tokenOwner); // Simulates the token owner context.
    shToken.mint(ALICE, 10);

    // Step 3: Check that ALICE's balance has been updated to 10.
    assertEq(shToken.balanceOf(ALICE), 10);

    // Step 4: Simulate an unauthorized burn attempt by BOB.
    // BOB should not be authorized to burn ALICE's tokens, testing the exploit scenario.
    vm.prank(BOB);
    shToken.burn(ALICE, 10);

    // Step 5: Verify that ALICE's balance has been reduced to zero after the burn.
    assertEq(shToken.balanceOf(ALICE), 0);
}

Run with the following command and observe how BOB can burn tokens that belong to ALICE:

forge test --via-ir -vvvv --mt test_UnauthorizedBurn

BVSS
Recommendation

Implement proper access control on the burn() function to restrict its usage to only authorized accounts. Integrate onlyRole() or an equivalent access modifier to ensure that only privileged roles can execute the burn operation. This change would prevent unauthorized actors from arbitrarily destroying tokens and protect user balances.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 120bb62 by implementing access control on the burn() function as recommended.

Remediation Hash

7.2 (HAL-07) Denial of Service in SHToken Transfers

// Critical

Description

The SHToken contract’s _updateUserBalance() and deleteUserFromArray() functions use an array, users, to keep track of addresses with non-zero balances. This design presents a critical Denial of Service (DoS) vulnerability that can be exploited by an attacker. Specifically, an attacker could transfer a minimal amount of tokens (e.g., 1 unit) to thousands of unique addresses, causing the users array to grow substantially in size. As the users array expands, the gas cost for iterating through it in the deleteUserFromArray() function and related functions becomes increasingly prohibitive. This could lead to failures in essential operations such as mint(), burn(), and transfer() due to gas limitations.


See the deleteUserFromArray() function, as it iterates through the users array to find and remove the specified user:

function deleteUserFromArray(address user) internal {
    for (uint256 i = 0; i < users.length; i++) {
        if (users[i] == user) {
            users[i] = users[users.length - 1];
            users.pop();
            break;
        }
    }
}

Because the users array can grow indefinitely, the gas cost of this loop could exceed the block gas limit when the array reaches a significant size, resulting in transaction failures and making the contract's core functions unusable.

Proof of Concept

The following Foundry test demonstrates this vulnerability:

function testFail_ArrayOverflowDoS() external {
    // Check 0 users using SHToken
    assertEq(shToken.getUsers().length, 0);

    // Step 1: Perform an initial mint of 1 unit to ALICE
    vm.prank(tokenOwner); // Simulate the context of the token owner.
    shToken.mint(ALICE, 1e6);

    // Step 2: Alice, as an attacker transfers 1 unit of tokens to 14_600 unique addresses.
    for (uint256 i = 0; i < 14_600; i++) {
        address recipient = address(uint160(i + 1)); // Create unique addresses starting from address 1.
        vm.prank(ALICE);
        shToken.transfer(recipient, 1);
    }

    // Verifies 14_600 addresses hold SHTokens
    assertEq(shToken.getUsers().length, 14_600);

    // Step 3: The token owner tries to mint some tokens to legit users and it gets OutOfGas errors
    for (uint256 i = 0; i < 100; i++) {
        address recipient = address(uint160(i + 100_000)); // Create unique addresses starting from address 100_000.
        vm.prank(tokenOwner);
        shToken.mint(recipient, 1);
    }
}

Run it with the following command and observe the OutOfGas error:

forge test --via-ir -vvvv --mt testFail_ArrayOverflowDoS

BVSS
Recommendation

Redesign the user-tracking mechanism to avoid direct iteration over a potentially large array. Consider using a more efficient data structure, such as a mapping or linked list, to manage user balances without needing to iterate through the entire list. This would allow for constant-time operations when adding or removing users, preventing gas issues and enhancing the contract’s scalability and resilience to DoS attacks.


Additionally, consider adding mechanisms to limit or manage the size of the users array to prevent unbounded growth and ensure that all core functions remain operational under reasonable gas limits.

Remediation

SOLVED: The SuperHedge team solved this finding in commit d3494d1 by removing the vulnerable functionality.

Remediation Hash

7.3 (HAL-09) Dual Mapping Flaw Causes Inaccurate Accounting and User List in SHToken

// Critical

Description

The SHToken contract suffers from a severe flaw in its accounting logic due to the use of two separate data structures: userBalances (custom) and _balances (inherited from OpenZeppelin's ERC20). This dual-mapping approach results in significant inconsistencies in user balance tracking, especially when functions like transferFrom() are used, which bypass custom tracking logic. As a result, users holding tokens may not appear in the users array, leading to inaccurate user reporting from the getUsers() function.


This issue can lead to incorrect user data, erroneous contract behavior, and potential vulnerabilities that undermine the reliability of user tracking and accounting.

Proof of Concept

The following Foundry test demonstrates this vulnerability:

function testFail_untracked_transferFrom() external {
    // Check 0 users using SHToken
    assertEq(shToken.getUsers().length, 0);

    // Step 1: Perform an initial mint of 1 unit to ALICE
    vm.prank(tokenOwner); // Simulate the context of the token owner.
    shToken.mint(ALICE, 1e6);

    // Check 1 user using SHToken
    assertEq(shToken.getUsers().length, 1);

    // Step 2: Alice transfers 1 token to BOB
    vm.startPrank(ALICE);
    shToken.transfer(BOB, 1);

    // Check 2 users using SHToken
    assertEq(shToken.getUsers().length, 2);

    // Step 3: Alice transfers 1 token to EVE
    shToken.approve(ALICE, 1e6);
    shToken.transferFrom(ALICE, EVE, 1);

    // Check 3 users using SHToken
    assertEq(shToken.getUsers().length, 3, "EVE has not been properly tracked");
}

Run with the following command and observe how using transferFrom() breaks the accounting and list of users:

forge test --via-ir -vvvv --mt testFail_untracked_transferFrom

BVSS
Recommendation

To address this issue, unify the balance-tracking logic by removing duplicate structures or ensuring all relevant functions update both userBalances and the users array consistently. Additionally, review and extend custom logic to functions such as transferFrom() to maintain accurate user tracking and reporting across all operations.

Remediation

SOLVED: The SuperHedge team solved this finding in commit d3494d1 by removing the vulnerable functionality.

Remediation Hash

7.4 (HAL-27) Broken setProductName() Due to Missing Implementation

// Medium

Description

The SHFactory contract features a setProductName() function designed to update the name of a product by invoking updateName() on ISHProduct:

/**
* @notice set new product name
*/
function setProductName(string memory _name, address _product) external onlyOwner {
  require(getProduct[_name] == address(0), "Product already exists");
  require(isProduct[_product], "Invalid product address");
  string memory _oldName = ISHProduct(_product).name();
  delete getProduct[_oldName];
  getProduct[_name] = _product;
  ISHProduct(_product).updateName(_name);

  emit ProductUpdated(_product, _name);
}

However, the SHProduct contract does not implement the updateName() function, even though it is specified in the ISHProduct interface. This oversight means that any call to setProductName() will revert, rendering the function ineffective and unusable. Without the updateName() implementation in SHProduct, setProductName() will always fail, preventing updates to product names and disrupting intended contract operations.


This issue highlights a larger problem noted in the "Missing Interface Inheritance" finding of this report: SHProduct does not inherit from ISHProduct. This results in non-compliance with the expected interface, leading to broken functionality and potential misunderstandings about the contract's behavior.

BVSS
Recommendation

To address this issue, implement a dedicated updateName() function in SHProduct that aligns with the ISHProduct interface specification. This function should only take the new name as input and update it accordingly. Doing so will ensure that setProductName() in SHFactory functions as intended without requiring unnecessary data or permissions. Additionally, make sure SHProduct inherits from ISHProduct to ensure compliance with the interface and prevent similar issues in the future.


Note: While SHProduct does have the updateParameters() function, which allows updating the product's name, it is not suitable for use as a direct replacement for updateName() in the setProductName() function. This is because updateParameters() requires additional parameters, including an IssuanceCycle struct and addresses for the router and market, making it more complex and restrictive for simple name updates.

Remediation

SOLVED: The SuperHedge team solved this finding in commit cd86de1 by removing the affected function.

Remediation Hash

7.5 (HAL-29) Incompatibilities With fee-on-transfer Tokens

// Low

Description

The current implementation of the deposit() function in the SHProduct contract does not account for fee-on-transfer tokens. When tokens are transferred to the contract, the deposit() function assumes that the transferred amount matches the _amount parameter specified by the user. However, fee-on-transfer tokens deduct a fee during the transfer process, resulting in the contract receiving less than the _amount. This discrepancy can cause issues, including blocked deposits and inconsistencies, as the minting and event emission operations are based on the _amount parameter rather than the actual amount received.

function deposit(uint256 _amount, bool _type) external whenNotPaused nonReentrant onlyAccepted{
    require(_amount > 0, "Greater zero");
    uint256 decimals = _currencyDecimals();
    
    uint256 amountToDeposit = _amount;
    if (_type == true) {
        amountToDeposit += userInfo[msg.sender].coupon + userInfo[msg.sender].optionPayout;
    }
    require((maxCapacity * 10 ** decimals) >= (currentCapacity + amountToDeposit), "Product is full");

    uint256 supply = amountToDeposit / (1 * 10 ** decimals);

    currency.safeTransferFrom(msg.sender, address(this), _amount);
    IERC20Token(tokenAddress).mint(msg.sender,_amount);
  ...
}

The SuperHedge Finance team indicated that any ERC20 token supported by Pendle Finance may be accepted as _currency, which includes over 30 tokens on Ethereum and a growing list. This flexibility further emphasizes the need to handle various token mechanics, including fee-on-transfer behavior.

BVSS
Recommendation

Implement checks to measure the contract's balance before and after the safeTransfer() and safeTransferFrom() operations to accurately determine the actual amount received, accounting for potential fee-on-transfer discrepancies. This approach ensures the correct amount is used for subsequent operations, avoiding issues with underfunded deposits. Alternatively, clearly document that fee-on-transfer tokens are not supported, so users are aware of this limitation.


The balance-based approach can be implemented as follows:

  • Record the balance of the contract before the transfer.

  • Perform the transfer operation.

  • Calculate the received amount by subtracting the pre-transfer balance from the post-transfer balance.


This method accounts for deflationary tokens and avoids potential issues with arbitrary ERC20 tokens that have non-standard transfer mechanisms.

Remediation

SOLVED: The SuperHedge team mitigated this finding by clearly documenting that fee-on-transfer tokens are not supported, so users are aware of this limitation.

7.6 (HAL-17) Lack of Tests and Incomplete NatSpec Documentation

// Low

Description

The audit revealed that the project lacks any form of test suite, which poses significant risks to the reliability and security of the smart contracts. Without tests, it is impossible to verify that the contract functions as intended or to identify potential vulnerabilities and edge cases.


Additionally, several code elements exhibit incomplete or inconsistent NatSpec documentation. This lack of comprehensive documentation can lead to misunderstandings during development and maintenance, potentially resulting in the misinterpretation of contract functionality or use.


Comprehensive test coverage and clear NatSpec documentation are essential for ensuring smart contract quality, maintainability, and safety.

BVSS
Recommendation

Implement Tests: Develop and integrate a complete test suite to validate all functionalities and scenarios within the project. Proper testing helps identify bugs, verify expected behaviors, and provide assurance of contract security.


Update NatSpec Documentation: Ensure that all functions and structures are fully documented with complete NatSpec annotations. Detailed documentation assists developers in understanding the code's purpose and parameters, facilitating future maintenance and auditing efforts.

Addressing these issues will significantly enhance the codebase's reliability, maintainability, and overall quality.

Remediation

SOLVED: The SuperHedge team solved this finding in commits 69beead, 38c3e52, 0f73d88 and 3ceab43 by adding tests and NatSpec documentation and unity tests as recommended.

Remediation Hash

7.7 (HAL-25) Unsafe ERC20 Operations

// Low

Description

The SHProduct contract includes several ERC20 operations that do not use OpenZeppelin's SafeERC20 library, which is considered best practice for handling token transfers securely. This can lead to potential issues if the ERC20 token used does not revert on failed transfers or approvals, resulting in silent failures that could affect the contract's accounting and state.


Instances where ERC20 operations are not using SafeERC20 or where return values are not verified include:


  • In the distributeFunds() function:

function distributeFunds(
    uint8 _yieldRate
) external onlyManager onlyLocked {
    require(!isDistributed, "Already distributed");
    require(_yieldRate <= 100, "Less than 100");
    uint8 optionRate = 100 - _yieldRate;

    uint256 optionAmount;
    if (optionRate > 0) {
        optionAmount = currentCapacity * optionRate / 100;
        currency.transfer(exWallet, optionAmount);
    }
...
}

  • In other parts of the contract:

IERC20(currencyAddress).approve(address(router), yieldAmount);

and:

IERC20(PT).approve(address(router), exactPtIn);

Using transfer(), transferFrom(), and approve() directly can be problematic if the token contract does not conform strictly to the ERC20 standard or does not return a boolean value. This can lead to undetected failures or vulnerabilities in certain edge cases.


These examples might not be exhaustive, and it is recommended to review the entire codebase for similar instances where SafeERC20 functions should be applied.

BVSS
Recommendation

It is recommended to consistently use OpenZeppelin's SafeERC20 library to ensure secure ERC20 operations. Replace direct calls like transfer(), transferFrom(), and approve() with safeTransfer(), safeTransferFrom(), and safeApprove() respectively. This approach ensures that the return values are correctly checked and that any failure in the operation results in a revert, thereby preventing silent failures and maintaining the integrity of the contract's state.


Using SafeERC20 functions will improve the security and reliability of the contract, especially when interacting with tokens that may not fully adhere to the ERC20 standard.

Remediation

PENDING: The SuperHedge team indicated that they will address this in the future.

7.8 (HAL-30) Division by Zero Not Prevented

// Low

Description

In SHProduct, the fundAccept(), deposit() and earlyWithdraw() functions perform division operations that are susceptible to division by zero errors. This could cause transactions to revert unexpectedly. For example:


  • In the fundAccept() function:

function fundAccept() external whenNotPaused onlyWhitelisted {
  uint256 totalSupply = IERC20(tokenAddress).totalSupply();
  ...
  userInfo[totalHolders[i]].optionPayout += prevSupply * _optionProfit / totalSupply;
...

If totalSupply() returns zero, this line will result in a division by zero, causing the transaction to revert. This situation may occur if no tokens have been minted yet, potentially disrupting any operations that rely on the successful execution of fundAccept().


  • In the deposit() function:

function deposit(uint256 _amount, bool _type) external whenNotPaused nonReentrant onlyAccepted{
  require(_amount > 0, "Greater zero");
  uint256 decimals = _currencyDecimals();
  ...
  uint256 supply = amountToDeposit / (1 * 10 ** decimals);
...

If decimals is zero, the division could lead to unintended behavior.


  • In the earlyWithdraw() function:

function earlyWithdraw(uint256 _noOfBlock) external onlyIssued{
  ...
  uint256 withdrawBlockSize = (issuanceCycle.underlyingSpotRef * 10**(decimals) * issuanceCycle.optionMinOrderSize)/10;
  uint256 totalBlock = currentToken / withdrawBlockSize;
...

If withdrawBlockSize is zero, the division by withdrawBlockSize will also result in a transaction revert.

BVSS
Recommendation

Introduce conditional checks to verify that totalSupply, supply, and withdrawBlockSize are greater than zero before performing these divisions. If any of these values are zero, consider reverting the transaction with an informative error message or handling the situation appropriately to avoid unexpected failures. This ensures that the fundAccept(), deposit(), and earlyWithdraw() functions handle edge cases gracefully and maintain robustness.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 53b0b93 by implementing the recommended checks.

Remediation Hash

7.9 (HAL-15) Limited Role Management and Unused DEFAULT_ADMIN_ROLE

// Low

Description

The SHToken contract utilizes OpenZeppelin's AccessControl for managing roles but only defines and grants the MINTER_ROLE, without including other roles such as a BURNER_ROLE for the burn() function. This could lead to security and operational challenges, as there is no specific access control for burning tokens or for other potentially sensitive functions. Additionally, the contract does not make use of the DEFAULT_ADMIN_ROLE, which limits flexibility in role assignment and management.


Note: The lack of access control in the burn() function, which allows unauthorized users to burn tokens from other accounts, has already been discussed in the finding titled "Anyone Can Burn SHToken From Other Users".


The SHProduct contract, while not directly using AccessControl, implements custom role management via modifiers such as onlyAdmin, onlyManager, and onlyWhitelisted. This contract could also benefit from adopting a structured approach using AccessControl or even OpenZeppelin's AccessControlDefaultAdminRules for better security and oversight of role management.


Implications:

  • Security Risks: The contract's minimal use of roles means that the burn() function and other potential privileged operations are not managed by specific access controls. This could lead to improper use if new restricted functions are added without sufficient role differentiation.


  • Role Management Limitations: Not using DEFAULT_ADMIN_ROLE or similar mechanisms restricts the contract's ability to securely manage role assignments and transitions, which could lead to role mismanagement or difficulty in future scalability.

BVSS
Recommendation

Introduce additional roles such as a BURNER_ROLE to manage access to the burn() function and ensure that only authorized accounts can execute this action. Utilize DEFAULT_ADMIN_ROLE in the SHToken contract to provide better oversight and role management. Additionally, consider implementing OpenZeppelin's AccessControlDefaultAdminRules to enforce best practices, such as a two-step process for role transfers, ensuring that transitions are secure and controlled.


For SHProduct, evaluate integrating AccessControl or AccessControlDefaultAdminRules to replace or enhance the custom role-checking logic (onlyAdmin, onlyManager, onlyWhitelisted). This approach standardizes role management, makes it easier to extend permissions, and provides a more transparent and auditable structure for maintaining contract security.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 120bb62 by implementing access control on the burn() function as recommended.

Remediation Hash

7.10 (HAL-04) Missing Input Validation

// Low

Description

During the security assessment, it was identified that some functions in the smart contracts lack proper input validation, allowing critical parameters to be set to undesired or unrealistic values. This can lead to potential vulnerabilities, unexpected behavior, or erroneous states within the contract.


Examples include, but are not limited to:

  • SHToken contract: The burn() function includes a check to ensure the amount argument is not zero. However, the mint() function does not have a similar validation, potentially allowing the minting of zero or unrealistic token amounts.

  • SHProduct contract: The functions updateCoupon(), updateParameters(), and updateStructure() lack checks for the validity of their input parameters, which could allow the contract to accept unrealistic or unintended values.

  • SHFactory contract: The initialize() function could benefit from input validation to prevent the use of incorrect or unexpected initial parameters.


This list is not exhaustive. It is recommended to conduct a comprehensive review of the codebase to identify and assess other functions that may require additional input validation. Ensuring appropriate checks are in place for critical parameters will enhance the overall reliability, security, and predictability of the contracts.

BVSS
Recommendation

To mitigate these issues, implement input validation in all constructor functions and other critical functions to ensure that inputs meet expected criteria. This can prevent unexpected behaviors and potential vulnerabilities.

Remediation

SOLVED: The SuperHedge team solved this finding in commits 9154686 and fba6ed6 by adding validations to different inputs as recommended.

Remediation Hash

7.11 (HAL-05) Single-step Ownership Transfer Process

// Low

Description

It was identified that the SHFactory contract inherited from OpenZeppelin's OwnableUpgradeable library. Ownership of the contracts that are inherited from the OwnableUpgradeable module can be lost, as the ownership is transferred in a single-step process.


The address that the ownership is changed to should be verified to be active or willing to act as the owner. Ownable2StepUpgradeable is safer than OwnableUpgradeable for smart contracts because the owner cannot accidentally transfer smart contract ownership to a mistyped address. Rather than directly transferring to the new owner, the transfer only completes when the new owner accepts ownership.

BVSS
Recommendation

To mitigate the risks associated with single-step ownership transitions and enhance contract security, it is recommended to adopt a two-step ownership transition mechanism, such as OpenZeppelin's Ownable2StepUpgradeable. This approach introduces an additional step in the ownership transfer process, requiring the new owner to accept ownership before the transition is finalized. The process typically involves the current owner calling a function to nominate a new owner, and the nominee then calling another function to accept ownership.


Implementing Ownable2StepUpgradeable provides several benefits:

1. Reduces Risk of Accidental Loss of Ownership: By requiring explicit acceptance of ownership, the risk of accidentally transferring ownership to an incorrect or zero address is significantly reduced.

2. Enhanced Security: It adds another layer of security by ensuring that the new owner is prepared and willing to take over the responsibilities associated with contract ownership.

3. Flexibility in Ownership Transitions: Allows for a smoother transition of ownership, as the nominee has the opportunity to prepare for the acceptance of their new role.


By adopting Ownable2StepUpgradeable, contract administrators can ensure a more secure and controlled process for transferring ownership, safeguarding against the risks associated with accidental or unauthorized ownership changes.

Remediation

SOLVED: The SuperHedge team solved this finding in commit fc013ba by switching from OwnableUpgradeable to Ownable2StepUpgradeable as recommended.

Remediation Hash

7.12 (HAL-31) Potential Rounding Errors in fundAccept()

// Low

Description

A potential issue was identified in the fundAccept() function of the SHProduct contract related to division operations that can lead to rounding errors and precision loss. The specific line involved is:

userInfo[totalHolders[i]].optionPayout += prevSupply * _optionProfit / totalSupply;

Solidity truncates the result of division operations, which can lead to rounding errors when the result includes fractional values. If prevSupply * _optionProfit is not properly scaled relative to totalSupply, the division result may be smaller than expected or even zero. This impacts the accuracy of optionPayout calculations and can result in users receiving less than their fair share or nothing at all when they should receive a non-zero amount.

BVSS
Recommendation

To mitigate precision loss and rounding errors, modify the code to apply a scaling factor to the numerator before performing the division. This approach helps maintain the accuracy of calculations by preserving fractional values. After performing the division, adjust the result by dividing by the same scaling factor to ensure the final value is correctly interpreted. It is also important to adapt downstream calculations accordingly to maintain consistency and avoid excessively large results.


Additionally, test the contract with edge cases that involve large and small values to confirm that the scaling solution works as intended and does not introduce unexpected behavior or inaccuracies. Implementing these changes will help ensure precise and fair optionPayout calculations, leading to better reliability and trust in the contract’s payout distribution logic.

Remediation

SOLVED: The SuperHedge team solved this finding in commit d3494d1 by removing the affected code.

Remediation Hash

7.13 (HAL-14) Owner Can Renounce Ownership

// Informational

Description

It was identified that the SHFactory contract inherited from OpenZeppelin's OwnableUpgradeable library. In the OwnableUpgradeable contracts, the renounceOwnership() function is used to renounce the Owner permission. Renouncing ownership before transferring would result in the contract having no owner, eliminating the ability to call privileged functions.

/**
 * @dev Leaves the contract without owner. It will not be possible to call
 * `onlyOwner` functions. Can only be called by the current owner.
 *
 * NOTE: Renouncing ownership will leave the contract without an owner,
 * thereby disabling any functionality that is only available to the owner.
 */
function renounceOwnership() public virtual onlyOwner {
    _transferOwnership(address(0));
}
BVSS
Recommendation

It is recommended that the Owner cannot call renounceOwnership() without first transferring ownership to another address. In addition, if a multi-signature wallet is used, the call to the renounceOwnership() function should be confirmed for two or more users.

Remediation

SOLVED: The SuperHedge team solved this finding in commit f9fa434 by overriding the renounceOwnership() function as recommended.

Remediation Hash

7.14 (HAL-12) Missing Interface Inheritance

// Informational

Description

The SHTokenFactory contract implements the ISHTokenFactory interface, but does not inherit from it. See its declaration:

contract SHTokenFactory {

Similarly, the SHProduct and SHToken contracts are expected to implement the ISHProduct and ISHToken interfaces respectively, but do not inherit from them either:

contract SHProduct is StructGen, ReentrancyGuardUpgradeable, PausableUpgradeable,EventFunctions{

When attempts were made to explicitly inherit from the ISHProduct and ISHToken interfaces, the compiler raised "Missing implementation" errors, indicating that required functions have not been fully implemented as per the interfaces.

BVSS
Recommendation

Ensure that both SHTokenFactory, SHProduct and SHToken explicitly inherit from their respective interfaces (ISHTokenFactory, ISHProduct and ISHToken) and that all required functions defined in these interfaces are implemented in the contracts. This practice improves code clarity, ensures consistency, and prevents potential interface discrepancies.

Remediation

SOLVED: The SuperHedge team solved this finding in commits 6297706, b58f557 and 1d3fc77 by making SHTokenFactory inherit from ISHTokenFactory as recommended. ISHTokenFactory and ISHProduct were removed.

Remediation Hash

7.15 (HAL-11) Use of Revert Strings Instead of Custom Error

// 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. Use the Custom Error in require or if statements.


Using custom errors inside require statements is only available from Solidity versions 0.8.26 and above. In case of older compiler version, consider using if statements + revert with Custom Error.

Remediation

ACKNOWLEDGED: The SuperHedge team indicated that they will not be addressing this finding.

7.16 (HAL-21) Cache Array Length Outside of Loop

// Informational

Description

When the length of an array is not cached outside of a loop, the Solidity compiler reads the length of the array during each iteration. For storage arrays, this results in an extra sload operation (100 additional gas for each iteration except the first). For memory arrays, this results in an extra mload operation (3 additional gas for each iteration except the first).


Detecting loops that use the length member of a storage array in their loop condition without modifying it can reveal opportunities for optimization.


See the following example in contracts/SHProduct.sol:

function storageOptionPosition(address[] memory _userList, uint256[] memory _amountList) external onlyIssued onlyAdmin
{
    for (uint256 i = 0; i < _userList.length; i++) 
    {
        UserOptionPositions.push(UserOptionPosition({
                userAddress: _userList[i],
                value: _amountList[i]
        }));
        totalOptionPosition += _amountList[i];
    }
}
BVSS
Recommendation

Cache the length of the array in a local variable outside the loop to optimize gas usage. This reduces the number of read operations required during each iteration of the loop. See the example fixes below:

function storageOptionPosition(address[] memory _userList, uint256[] memory _amountList) external onlyIssued onlyAdmin
{
  uint256 length = _userList.length;
    for (uint256 i = 0; i < length; i++) 
    {
        UserOptionPositions.push(UserOptionPosition({
                userAddress: _userList[i],
                value: _amountList[i]
        }));
        totalOptionPosition += _amountList[i];
    }
}
Remediation

SOLVED: The SuperHedge team solved this finding in commits c480743 and 2bdd1fb by implementing the recommended changes.

Remediation Hash

7.17 (HAL-01) Multiple Unlocked Solidity Versions in Use

// Informational

Description

The contracts in scope use multiple pragma versions with unlocked versions. Contracts should be deployed with the same compiler version and flags with which they were thoroughly tested. Locking the pragma helps to ensure that contracts are not accidentally deployed using another pragma, for example, either an outdated pragma version that could introduce bugs that negatively affect the contract system or a recently released pragma version that has not been extensively tested.

**Reference: https://github.com/ethereum/solidity/releases**

In the Solidity GitHub repository, there is a json file where are all the bugs found in the different compiler versions.

**Reference: https://github.com/ethereum/solidity/blob/develop/docs/bugs_by_version.json**"

BVSS
Recommendation

Consider locking the pragma version. When possible, do not use floating pragmas. Specifying a fixed compiler version ensures that the bytecode produced does not vary between builds. This is especially important if you rely on bytecode-level verification of the code.

Remediation

SOLVED: The SuperHedge team solved this finding in commit b58f557 by implementing recommended fix.

Remediation Hash

7.18 (HAL-02) Use of Unlicensed Smart Contracts

// Informational

Description

The StructGen.sol smart contract is marked as unlicensed, as indicated by the SPDX license identifier at the top of the file:

// SPDX-License-Identifier: UNLICENSED

Using unlicensed contract can lead to legal uncertainties and potential conflicts regarding the usage, modification and distribution rights of the code. This may deter other developers from using or contributing to the project and could potentially lead to legal issues in the future.

Score
Recommendation

It is strongly recommended to choose and apply an appropriate open-source license to the smart contract. Some popular options for blockchain and smart contract projects include:


  1. MIT License: A permissive license that allows for reuse with minimal restrictions.

  2. GNU General Public License (GPL): A copyleft license that ensures derivative works are also open-source.

  3. Apache License 2.0: A permissive license that provides an express grant of patent rights from contributors to users.

Remediation

SOLVED: The SuperHedge team solved this finding in commit e7c09e2 by implementing recommended fix.

Remediation Hash

7.19 (HAL-03) Lack of Event Emission

// Informational

Description

It has been observed that some functionalities are missing emitting events. Events are a method of informing the transaction initiator about the actions taken by the called function. It logs its emitted parameters in a specific log history, which can be accessed outside of the contract using some filter parameters. Events help non-contract tools to track changes, and events prevent users from being surprised by changes.


Examples:

  • Some examples from the SHProduct contract: the functions whitelist(), removeFromWhitelist() and addAdmin().

  • Some examples from the SHToken contract: the _updateUserBalance() or deleteUserFromArray() functions.


This list is not exhaustive, and it is recommended to review the entire codebase to identify additional instances where event emissions may be beneficial. A thorough analysis should be conducted to determine whether adding event emissions aligns with the intended design and provides value in tracking state changes.

Score
Recommendation

All functions updating important parameters should emit events.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 3a5fb93 by implementing recommended fix.

Remediation Hash

7.20 (HAL-08) Redundant Use of SafeMath

// Informational

Description

The SHToken contract imports and uses OpenZeppelin's SafeMath library. However, since Solidity 0.8 and later versions have built-in overflow and underflow protection, the use of SafeMath is redundant:

* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
Score
Recommendation

Consider removing the use of SafeMath to simplify the code and reduce unnecessary dependencies. If mathematical functions beyond basic operations are required, consider using OpenZeppelin's Math.sol from version 5.0 or higher. This ensures up-to-date practices and leverages more advanced utility functions without redundant overflow checks.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 358750a by implementing recommended fix.

Remediation Hash

7.21 (HAL-10) Style Guide Optimizations

// Informational

Description

In Solidity development, adhering to the official style guide is best practice to ensure code consistency, readability, and maintainability. Throughout the contracts, there are several instances where the code does not follow these guidelines. Some examples include:

  • Function Visibility: Some public functions are not called within the contract that declares them and could be declared as external for better optimization.


  • Function Modifiers: view functions, such as decimals() in SHToken, could be declared as pure when they do not access any state variables.


  • Naming Conventions: Internal functions, like deleteUserFromArray() in SHToken, should follow the convention of starting with an underscore (e.g., _deleteUserFromArray()).


  • State Variable Naming: Internal state variables, such as users in SHToken, should start with an underscore to differentiate them from local variables (e.g., _users).


  • Formatting Issues: The SHProduct contract has instances of inconsistent formatting, such as extra spaces before semicolons (IPAllActionV3 public router ;).


  • Inheritance Formatting: The SHProduct contract's inheritance declaration lacks spacing consistency:

contract SHProduct is StructGen, ReentrancyGuardUpgradeable, PausableUpgradeable,EventFunctions{

  • Event Argument Naming: Events in contracts/libraries/EventFunctions.sol have arguments with leading underscores, which is not typical practice:

event DistributeFunds(
    address indexed _exWallet,
    uint256 _optionRate,
    address indexed _pendleRouter,
    uint8 _yieldRate
);

event RedeemYield(
    address _pendleRouter,
    uint256 _amount
);

This list is not exhaustive, and it is recommended to review the entire codebase to identify other areas where style guide improvements can be made.

Score
Recommendation

Apply the following style guide enhancements throughout the codebase:

  • Evaluate all public functions to determine if they can be declared as external.

  • Review all view functions to check if they can be declared as pure.

  • Prefix internal function names and state variables with an underscore.

  • Ensure consistent spacing and formatting, especially in inheritance declarations and other code structures.

  • Remove leading underscores from event argument names.

Implementing these style optimizations will improve the code's readability, consistency, and maintainability, aligning it with Solidity best practices.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 6b8e517 by implementing recommended fix.

Remediation Hash

7.22 (HAL-13) Redundant Code

// Informational

Description

The burn() function in the SHToken contract includes redundant code that can be simplified for better readability and efficiency. Specifically, the burn() function performs a check for balanceOf(account) >= amount:

require(balanceOf(account) >= amount, "Insufficient balance");
_burn(account, amount);

However, the internal _burn() function, inherited from OpenZeppelin's ERC20 contract, already includes a balance check to ensure that the account holds enough tokens before proceeding. This makes the additional balance check in the burn() function redundant.


Implications:

  • Code Redundancy: Redundant checks can increase code complexity without providing additional security benefits.

  • Gas Costs: While minimal, performing unnecessary checks can slightly increase gas consumption for each call to the burn() function.

Score
Recommendation

Remove the require(balanceOf(account) >= amount, "Insufficient balance"); line from the burn() function. This change will streamline the code by relying on the internal _burn() function’s built-in check, improving readability and maintaining efficient operation without redundant logic.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 5a1649c by implementing recommended fix.

Remediation Hash

7.23 (HAL-16) Commented Out Code

// Informational

Description

During the security assessment, several instances of commented-out code were found in the SHProduct contract. Commented-out code, especially if it is outdated or unused, can clutter the codebase, reduce readability, and make maintenance more difficult. Additionally, it can cause confusion among developers and auditors about the purpose and functionality of the code.


Examples include:

  • Commented-out import:

// import "./interfaces/ISHFactory.sol";

  • Commented-out function calls:

// IERC20(tokenAddress).transferFrom(msg.sender, deadAddress, currentToken);

  • Commented-out documentation:

// /**
//  * @notice Withdraws user's option payout
//  */

  • Commented-out logic:

// (netTokenOut,,) = router.swapExactPtForToken(
// address(this), address(market), exactPtIn, createTokenOutputStruct(currencyAddress, 0), emptyLimit);

These examples might not be exhaustive, and it is recommended to review the entire codebase for additional instances of commented-out code.

Score
Recommendation

It is advised to remove or uncomment code that is no longer needed or should be active. Keeping the codebase clean and up-to-date improves readability and maintainability. If certain parts of the code are kept for reference, consider documenting why they are commented out and whether they are part of future plans or deprecated. This ensures that the code remains organized and avoids confusion for future development and auditing.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 3193b05 by implementing recommended fix.

Remediation Hash

7.24 (HAL-19) Incorrect and Ambiguous Error Messages

// Informational

Description

The codebase includes instances where error messages are incorrect or ambiguous, potentially leading to confusion when transactions revert. Clear and precise error messages are crucial for understanding why a function failed, aiding both developers and users in debugging and ensuring smooth interactions with the contract. Examples from the SHProduct contract include:


  • Misleading or Inaccurate Modifier Messages:

modifier LockedOrMature() {
    require(status == DataTypes.Status.Locked || status == DataTypes.Status.Mature, 
        "Neither mature nor locked");
    _;
}

modifier AcceptedOrLockedOrMature() {
    require(status == DataTypes.Status.Locked || status == DataTypes.Status.Mature || status == DataTypes.Status.Accepted, 
        "Neither mature nor locked");
    _;
}

The error message "Neither mature nor locked" in the AcceptedOrLockedOrMature modifier does not accurately reflect the condition being checked. It can be misleading, especially since the modifier also checks for status == DataTypes.Status.Accepted.


  • Vague Error Messages:

function whitelist(address _account) external onlyManager {
    require(!whitelisted[_account], "Whitelisted");
    whitelisted[_account] = true;
}

The message "Whitelisted" could be more descriptive, such as "Account is already whitelisted".

function withdrawCoupon() external nonReentrant {
  uint256 _couponAmount = userInfo[msg.sender].coupon;
  require(_couponAmount > 0, "No CP");
  require(totalBalance() >= _couponAmount, "Balance");
...

The error message "No CP" is cryptic and could be improved to "No coupon payout available". Similarly, "Balance" could be changed to "Insufficient contract balance" for better clarity.


These examples might not be exhaustive, and a thorough review of the codebase is recommended to identify and improve other unclear or incorrect error messages.

Score
Recommendation

Ensure that all error messages are descriptive and accurately reflect the conditions being checked. This helps users and developers understand the cause of a revert and enhances the maintainability of the contract. Revisiting error messages and updating them for clarity and accuracy will improve the user experience and simplify debugging.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 0e6469f by implementing recommended fix.

Remediation Hash

7.25 (HAL-20) Redundant Boolean Equality Checks

// Informational

Description

Two instances of redundant boolean equality checks were found in contracts/SHProduct.sol:

if (_type == true) {

This style adds unnecessary complexity to the code.

Score
Recommendation

Simplify the code by removing the explicit equality check:

if (_type) {

This approach is cleaner and aligns with best practices for readability and maintainability.

Remediation

SOLVED: The SuperHedge team solved this finding in commits 3e30fbb and 3a16255 by implementing recommended fix.

Remediation Hash

7.26 (HAL-22) Consider Using Named Mappings

// Informational

Description

The project accepts using a Solidity compiler 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/SHFactory.sol, instead of declaring:

/// @notice Mapping from product name to product address
mapping(string => address) public getProduct;

It could be declared as:

mapping(string productName => address productAddress) public getProduct;
Remediation

SOLVED: The SuperHedge team solved this finding in commit d5b0f8c by implementing recommended fix.

Remediation Hash

7.27 (HAL-23) Redundant Use of block.timestamp in Event Parameters

// Informational

Description

The EventFunctions contract includes events that emit block.timestamp as a parameter. For example:

event FundLock(
  uint256 _timestamp
);

This use of block.timestamp as an event parameter is redundant because the timestamp of when the event is emitted is already included in the transaction metadata and can be accessed directly from the event logs. Including it as an explicit parameter increases the size of the emitted event, leading to slightly higher gas costs without adding meaningful value.

Score
Recommendation

Remove block.timestamp as a parameter from event declarations unless it serves a specific, necessary purpose beyond the default timestamping available in transaction logs. This will make the event more concise, reduce gas usage, and maintain optimal efficiency in the contract.

Remediation

SOLVED: The SuperHedge team solved this finding in commit 7d1757e by removing block.timestamp as a parameter from event declarations, as recommended.

Remediation Hash

7.28 (HAL-24) Unused Imports and Library

// Informational

Description

During the security assessment, several unused imports and libraries were found across the codebase, potentially increasing complexity and clutter. Unused code can lead to confusion, unnecessary dependencies, and potential maintenance challenges. Examples include:


  • Unused Import in SHProduct:

import "./interfaces/ISHProduct.sol";

This import is present in the SHProduct contract but is not used anywhere in the code.


  • Unused Import in SHToken:

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

This import is included but not referenced in the SHToken contract.


  • Unused Library in SHProduct:

import "./libraries/Array.sol";

Although Array is imported and referenced with using Array for address[];, none of the address[] variables utilize its remove() function or any other functionality provided by the library.


These examples might not cover all instances, so it is recommended to review the entire codebase for additional unused imports and libraries.

Score
Recommendation

For better clarity, consider removing all unused code. Keeping the code clean and relevant helps in maintaining a secure and efficient codebase.

If the imported library or functionality is intended for future use, document this in the code to avoid confusion.

Remediation

SOLVED: The SuperHedge team solved this finding in commits a919cb8 and 127a22f by removing removing the unused code as recommended.

Remediation Hash

7.29 (HAL-26) Magic Numbers and Lack of Scientific Notation

// Informational

Description

In programming, magic numbers refer to the use of unexplained numerical or string values directly in code, without any clear indication of their purpose or origin. The use of magic numbers can lead to confusion and make the code more difficult to understand, maintain, and update.


To improve the readability and maintainability of smart contracts, it is recommended to avoid using magic numbers and instead use named constants or variables to represent these values. By doing so, you provide clear context for the values, making it easier for developers to understand their purpose and significance.


Additionally, unnecessary operations, such as multiplying by 1, can make the code less efficient and harder to read. Examples found in the codebase include:


  • In SHProduct:

uint256 _amount = prevSupply * issuanceCycle.coupon / 10000;

The value 10000 is used without context, making it unclear what it represents.

And:

uint256 supply = amountToDeposit / (1 * 10 ** decimals);

The expression "1 * 10 ** decimals" includes a redundant 1 operation that could be simplified to "10 ** decimals" for better clarity.


  • In SHToken:

return 6;

The number 6 is returned without explanation, which can be confusing for those unfamiliar with its significance.


  • In StructGen:

ApproxParams public defaultApprox = ApproxParams(0, type(uint256).max, 0, 256, 1e14);

The value 256 and 1e14 could benefit from a named constant to clarify its purpose.


These examples may not be exhaustive, and a comprehensive review of the codebase is recommended to identify and refactor other instances of magic numbers and unnecessary operations.

Score
Recommendation

To improve code maintainability, readability, and reduce the risk of potential errors, replace magic numbers with well-defined constants. This approach helps provide clear and descriptive names for values, making the code easier to understand and maintain. Simplify unnecessary operations, such as 1 *, to streamline code readability. Additionally, updating values becomes more straightforward, as changes can be made in one location, reducing the risk of inconsistencies. For large numbers, consider using scientific notation (e.g., 1e4).

Remediation

SOLVED: The SuperHedge team solved this finding in commits 8e5d907 and a0eb41f by removing magic numbers as recommended. Notice, however, that regular global state variables were used instead of constants.

Remediation Hash

7.30 (HAL-28) Incorrect Order of Modifiers: nonReentrant Should Precede All Other Modifiers

// Informational

Description

To mitigate the risk of reentrancy attacks, a modifier named nonReentrant is commonly used. This modifier acts as a lock, ensuring that a function cannot be called recursively while it is still in execution. A typical implementation of the nonReentrant modifier locks the function at the beginning and unlocks it at the end. However, it is critical to place the nonReentrant modifier before all other modifiers in a function. Placing it first ensures that all other modifiers cannot bypass the reentrancy protection. In the current implementation, some functions use other modifiers before nonReentrant, which compromises the protection it provides.


For example, in the deposit() function below, the nonReentrant is placed after two other modifier calls, a reentrancy attack could potentially bypass the lock and manipulate the contract by exploiting the privileges of the owner:

function deposit(uint256 _amount, bool _type) external whenNotPaused nonReentrant onlyAccepted{

The risk of this finding was decreased to informational risk after reviewing that the whenNotPaused modifier of the deposit() function is not interacting with other addresses.

Score
Recommendation

By following the best practice of placing the nonReentrant modifier before all other modifiers, one can significantly reduce the risk of reentrancy-related vulnerabilities. This is simple yet effective approach can help augment the security posture of any Solidity smart contract.

Remediation

SOLVED: The SuperHedge team solved this finding in commit dce0ad0 by placing the nonReentrant modifier before all other modifiers as recommended.

Remediation Hash

7.31 (HAL-32) Checks-Effects-Interactions Pattern Not Followed

// Informational

Description

In the SHProduct contract, multiple functions do not adhere to the checks-effects-interactions (CEI) pattern, a recommended best practice in Solidity that helps prevent reentrancy vulnerabilities. The CEI pattern dictates that functions should first perform checks (validating conditions), then make state variable updates (effects), and finally make external calls (interactions).


Below are examples where this pattern is not followed:

  1. deposit() Function:

    • The function makes an external call to currency.safeTransferFrom() and IERC20Token(tokenAddress).mint() before updating state variables like currentCapacity and userInfo[msg.sender].

  2. distributeFunds() Function:

    • External calls to currency.transfer(), IERC20(currencyAddress).approve(), and router.swapExactTokenForPt() occur before updating isDistributed.

  3. earlyWithdraw() Function:

    • External calls to IERC20Token(tokenAddress).burn() and IERC20(PT).approve() are made before updating currentCapacity and netPtOut.

  4. redeemYield() Function:

    • External calls to IERC20(PT).approve() and router.redeemPyToToken() occur before updating isDistributed.

Score
Recommendation

To mitigate potential reentrancy risks, adhere to the CEI pattern by updating state variables (effects) before making any external calls (interactions). For instance:

  • Update state variables such as currentCapacity, isDistributed, and userInfo[msg.sender].

  • Make the external call afterward.

Rearranging the code to follow the CEI pattern ensures that all relevant state changes are made before any interactions with external contracts, reducing the risk of reentrancy attacks and enhancing the overall security of the contract.

Remediation

SOLVED: The SuperHedge team solved this finding in commit b788a5a by implementing recommended fix.

Remediation Hash

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 contracts. 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.

All issues identified by Slither were proved to be false positives or have been added to the issue list in this report.

Output

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.

// Download the full report

* Use Google Chrome for best results

** Check "Background Graphics" in the print settings if needed

© Halborn 2024. All rights reserved.