Halborn Logo

Safety Module - Biconomy


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: March 15th, 2022 - March 28th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

8

Critical

0

High

0

Medium

0

Low

3

Informational

5


1. INTRODUCTION

\client engaged Halborn to conduct a security audit on their smart contracts beginning on 2022-03-15 and ending on 2022-03-28. The security assessment was scoped to the smart contracts provided to the Halborn team.

2. AUDIT SUMMARY

The team at Halborn was provided two weeks for the engagement and assigned a full-time security engineer to audit the security of the smart contract. The security engineer is a blockchain and smart-contract security expert with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.

The purpose of this audit is to:

    • Ensure that smart contract functions operate as intended

    • Identify potential security issues with the smart contracts

In summary, Halborn identified some security risks that were mostly addressed by the \client team.

3. TEST APPROACH & METHODOLOGY

Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of the smart contract audit. 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 audit:

    • Research into architecture and purpose.

    • Smart Contract manual code review and walkthrough.

    • Graphing out functionality and contract logic/connectivity/functions

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

    • Dynamic Analysis (ganache-cli, brownie, hardhat)

    • Static Analysis(slither)

4. SCOPE

\begin{enumerate} \item \href{https://github.com/bcnmy/SM-smart-contract/tree/master/contracts}{Safety Module} \begin{enumerate} \item AaveDistributionManager.sol \item BicoProtocolEcosystemReserve.sol \item DistributionTypes.sol \item InitializableAdminUpgradeabilityProxy.sol \item StakedTokenBptRev2.sol \item StakedTokenV3.sol \item lib/GovernancePowerDelegationERC20.sol \item lib/GovernancePowerWithSnapshot.sol \item lib/VersionedInitializable.sol \end{enumerate} \item Out-of-Scope Contracts \begin{enumerate} \item helper/.sol \item interface/.sol \item lib/.sol \item mock/.sol \end{enumerate} \end{enumerate}

5. RISK METHODOLOGY

Vulnerabilities or issues observed by Halborn are ranked based on the risk assessment methodology by measuring the LIKELIHOOD of a security incident and the IMPACT should an incident occur. This framework works for communicating the characteristics and impacts of technology vulnerabilities. The quantitative model ensures repeatable and accurate measurement while enabling users to see the underlying vulnerability characteristics that were used to generate the Risk scores. For every vulnerability, a risk level will be calculated on a scale of 5 to 1 with 5 being the highest likelihood or impact.
RISK SCALE - LIKELIHOOD
  • 5 - Almost certain an incident will occur.
  • 4 - High probability of an incident occurring.
  • 3 - Potential of a security incident in the long term.
  • 2 - Low probability of an incident occurring.
  • 1 - Very unlikely issue will cause an incident.
RISK SCALE - IMPACT
  • 5 - May cause devastating and unrecoverable impact or loss.
  • 4 - May cause a significant level of impact or loss.
  • 3 - May cause a partial impact or loss to many.
  • 2 - May cause temporary impact or loss.
  • 1 - May cause minimal or un-noticeable impact.
The risk level is then calculated using a sum of these two values, creating a value of 10 to 1 with 10 being the highest level of security risk.
Critical
High
Medium
Low
Informational
  • 10 - CRITICAL
  • 9 - 8 - HIGH
  • 7 - 6 - MEDIUM
  • 5 - 4 - LOW
  • 3 - 1 - VERY LOW AND INFORMATIONAL

6. SCOPE

Out-of-Scope: New features/implementations after the remediation commit IDs.

7. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

0

Low

3

Informational

5

Impact x Likelihood

HAL-02

HAL-03

HAL-05

HAL-06

HAL-07

HAL-08

HAL-04

HAL-01

Security analysisRisk levelRemediation Date
IGNORED RETURN VALUESLowSolved - 04/08/2022
MISSING ZERO ADDRESS CHECKSLowSolved - 04/08/2022
MISSING REENTRANCY GUARDLowSolved - 04/08/2022
EXPERIMENTAL KEYWORD USAGEInformationalAcknowledged
FLOATING PRAGMAInformationalSolved - 04/08/2022
USE 1E18 CONSTANT FOR GAS OPTIMIZATIONInformationalSolved - 04/08/2022
MISUSE OF PUBLIC FUNCTIONSInformationalSolved - 04/08/2022
USE ++I INSTEAD OF I++ IN LOOPS FOR GAS OPTIMIZATIONInformationalAcknowledged

8. Findings & Tech Details

8.1 IGNORED RETURN VALUES

// Low

Description

The return value of an external call is not stored in a local or state variable. In the BicoProtocolEcosystemReserve.sol contract, there is a function that ignores the return value.

Code Location

BicoProtocolEcosystemReserve.sol

 function transfer(
    IERC20 token,
    address recipient,
    uint256 amount
  ) external onlyFundsAdmin {
    token.transfer(recipient, amount);
  }
Score
Impact: 1
Likelihood: 3
Recommendation

SOLVED: The issue was solved in commit ID: 20611133e6edd306f15840a2e5735577f1f08050

8.2 MISSING ZERO ADDRESS CHECKS

// Low

Description

Safety Module contracts have address fields in multiple functions. These functions are missing address validations. Each address should be validated and checked to be non-zero. This is also considered as a best practice.

During testing, it has been found that some of these inputs are not protected against using address(0) as the target address.

Code Location

AaveDistributionManager.sol

constructor(address emissionManager, uint256 distributionDuration) public {
    DISTRIBUTION_END = block.timestamp.add(distributionDuration);
    EMISSION_MANAGER = emissionManager;
  }

BicoProtocolEcosystemReserve.sol

function _setFundsAdmin(address admin) internal {
    _fundsAdmin = admin;
    emit NewFundsAdmin(admin);
  }

StakedTokenBptRev2.sol

constructor(
    IERC20 stakedToken,
    IERC20 rewardToken,
    uint256 cooldownSeconds,
    uint256 unstakeWindow,
    address rewardsVault,
    address emissionManager,
    uint128 distributionDuration,
    string memory name,
    string memory symbol,
    uint8 decimals,
    address governance
  ) public ERC20(name, symbol) AaveDistributionManager(emissionManager, distributionDuration) {
    STAKED_TOKEN = stakedToken;
    REWARD_TOKEN = rewardToken;
    COOLDOWN_SECONDS = cooldownSeconds;
    UNSTAKE_WINDOW = unstakeWindow;
    REWARDS_VAULT = rewardsVault;
    _aaveGovernance = ITransferHook(governance);
    ERC20._setupDecimals(decimals);
  }

StakedTokenV3.sol

constructor(
    IERC20 stakedToken,
    IERC20 rewardToken,
    uint256 cooldownSeconds,
    uint256 unstakeWindow,
    address rewardsVault,
    address emissionManager,
    uint128 distributionDuration,
    string memory name,
    string memory symbol,
    uint8 decimals,
    address governance
  ) public ERC20(name, symbol) AaveDistributionManager(emissionManager, distributionDuration) {
    STAKED_TOKEN = stakedToken;
    REWARD_TOKEN = rewardToken;
    COOLDOWN_SECONDS = cooldownSeconds;
    UNSTAKE_WINDOW = unstakeWindow;
    REWARDS_VAULT = rewardsVault;
    _aaveGovernance = ITransferHook(governance);
    ERC20._setupDecimals(decimals);
  }
Score
Impact: 2
Likelihood: 2
Recommendation

SOLVED: The issue was solved in commit ID: 20611133e6edd306f15840a2e5735577f1f08050

8.3 MISSING REENTRANCY GUARD

// Low

Description

To protect against cross-function re-entrancy attacks, it may be necessary to use a mutex. By using this lock, an attacker can no longer exploit the withdrawal function with a recursive call. OpenZeppelin has its own mutex implementation called ReentrancyGuard which provides a modifier to any function called nonReentrant that protects the function with a mutex against re-entrancy attacks.

Code Location

Missing Reentrancy Guard - Functions

StakedTokenBptRev2::redeem()
StakedTokenBptRev2::claimRewards()
StakedTokenV3::redeem()
StakedTokenV3::claimRewards()
Score
Impact: 2
Likelihood: 2
Recommendation

SOLVED: The issue was solved in commit ID: 20611133e6edd306f15840a2e5735577f1f08050

8.4 EXPERIMENTAL KEYWORD USAGE

// Informational

Description

ABIEncoderV2 is enabled and using experimental features could be dangerous in live deployments. The experimental ABI encoder does not handle non-integer values shorter than 32 bytes properly. This applies to bytesNN types, bool, enum and other types when they are part of an array or a struct and encoded directly from storage. This means these storage references have to be used directly inside abi.encode(...) as arguments in external function calls or in event data without prior assignment to a local variable. The types bytesNN and bool will result in corrupted data, while enum might lead to an invalid revert.

Code Location

AaveDistributionManager.sol

pragma experimental ABIEncoderV2;

StakedTokenBptRev2.sol

pragma experimental ABIEncoderV2;

StakedTokenV3.sol

pragma experimental ABIEncoderV2;
Score
Impact: 1
Likelihood: 2
Recommendation

ACKNOWLEDGED: The Biconomy team acknowledged this issue.

8.5 FLOATING PRAGMA

// Informational

Description

The project contains many instances of floating pragma. Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, either an outdated compiler version that might introduce bugs that affect the contract system negatively or a pragma version too recent which has not been extensively tested.

Code Location

Floating Pragma

AaveDistributionManager.sol::pragma solidity ^0.7.5;
DistributionTypes.sol::pragma solidity ^0.7.5;
StakedTokenBptRev2.sol::pragma solidity ^0.7.5;
StakedTokenV3.sol::pragma solidity ^0.7.5;
GovernancePowerDelegationERC20.sol::pragma solidity ^0.7.5;
GovernancePowerWithSnapshot.sol::pragma solidity ^0.7.5;
VersionedInitializable.sol::pragma solidity ^0.7.5;
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The issue was solved in commit ID: 20611133e6edd306f15840a2e5735577f1f08050

8.6 USE 1E18 CONSTANT FOR GAS OPTIMIZATION

// Informational

Description

In Solidiy, the exponentiation operation (**) costs up to 10 gas. It is possible to consume less gas to calculate the prices of the tokens if DECIMAL variable is fixed.

Code Location

AaveDistributionManager

function _getRewards(
    uint256 principalUserBalance,
    uint256 reserveIndex,
    uint256 userIndex
  ) internal pure returns (uint256) {
    return principalUserBalance.mul(reserveIndex.sub(userIndex)).div(10**uint256(PRECISION));
  }

AaveDistributionManager

uint256 currentTimestamp =
      block.timestamp > DISTRIBUTION_END ? DISTRIBUTION_END : block.timestamp;
    uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp);
    return
      emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add(
        currentIndex
      );
  }
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The issue was solved in commit ID: 20611133e6edd306f15840a2e5735577f1f08050

8.7 MISUSE OF PUBLIC FUNCTIONS

// Informational

Description

In public functions, the array arguments are immediately copied into memory, while external functions can read directly from the calldata. Reading calldata is cheaper than allocating memory.

Public functions need to write arguments to memory because public functions can be called internally. Internal calls are passed internally via pointers to memory. Therefore, a function expects its arguments to be located in memory when the compiler generates the code for an internal function.

Code Location

Misuse of Public Functions

AaveDistributionManager.getUserAssetData(address,address)
BicoProtocolEcosystemReserve.setFundsAdmin(address)
StakedTokenBptRev2.delegateByTypeBySig(address,IGovernancePowerDelegationToken.DelegationType,uint256,uint256,uint8,bytes32,bytes32)
StakedTokenBptRev2.delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)

Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The issue was solved in commit ID: 20611133e6edd306f15840a2e5735577f1f08050

8.8 USE ++I INSTEAD OF I++ IN LOOPS FOR GAS OPTIMIZATION

// Informational

Description

In all the loops, the variable i is incremented using i++. It is known that, in loops, using ++i costs less gas per iteration than i++. This also affects variables incremented inside the loop code block.

Code Location

AaveDistributionManager.sol

for (uint256 i = 0; i < assetsConfigInput.length; i++) {
      AssetData storage assetConfig = assets[assetsConfigInput[i].underlyingAsset];

AaveDistributionManager.sol

for (uint256 i = 0; i < stakes.length; i++) {
      accruedRewards = accruedRewards.add(
        _updateUserAssetInternal(

AaveDistributionManager.sol

 for (uint256 i = 0; i < stakes.length; i++) {
      AssetData storage assetConfig = assets[stakes[i].underlyingAsset];
Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: The Biconomy team acknowledged this issue.

9. Automated Testing

STATIC ANALYSIS REPORT

Description

Halborn used automated testing techniques to enhance coverage of certain areas of the scoped contract. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified all the contracts in the repository and was able to compile them correctly into their ABI and binary formats. 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.

Results

slither1.pngslither2.pngslither3.png

As a result of the tests carried out with the Slither tool, some results were obtained and these results were reviewed by Halborn. Based on the results reviewed, some vulnerabilities were determined to be false positives and these results were not included in the report. The actual vulnerabilities found by Slither are already included in the report findings.

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.