Halborn Logo

Aura Finance


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/25/2024

Date of Engagement by: May 16th, 2022 - June 28th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

6

Critical

0

High

0

Medium

0

Low

2

Informational

4


1. INTRODUCTION

Aura Finance engaged Halborn to conduct a security audit on their smart contracts beginning on May 16th, 2022 and ending on June 28th, 2022. The security assessment was scoped to the smart contracts provided in the contracts GitHub repository aurafinance/aura-contracts.

2. AUDIT SUMMARY

The team at Halborn was provided six weeks for the engagement and assigned one 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 few security risks that were accepted and acknowledged by the Aura Finance team.

3. TEST APPROACH & 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 audit. 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 audit:

    • 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

    • Scanning of solidity files for vulnerabilities, security hotspots or bugs. (MythX)

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

    • Testnet deployment (Brownie, Remix IDE)

4. SCOPE

IN-SCOPE: The security assessment was scoped to the following smart contracts:

aura-contracts:

    • Aura.sol

    • AuraBalRewardPool.sol

    • AuraClaimZap.sol

    • AuraLocker.sol

    • AuraMath.sol

    • AuraMerkleDrop.sol

    • AuraMinter.sol

    • AuraPenaltyForwarder.sol

    • AuraStakingProxy.sol

    • AuraVestedEscrow.sol

    • BalInvestor.sol

    • BalLiquidityProvider.sol

    • CrvDepositorWrapper.sol

    • ExtraRewardsDistributor.sol

    • RewardPoolDepositWrapper.sol

convex-platform:

    • BaseRewardPool.sol

    • VirtualBalanceRewardPool.sol

    • ProxyFactory.sol

    • DepositToken.sol

    • ExtraRewardStashV3.sol

    • RewardFactory.sol

    • cCrv.sol

    • BaseRewardPool4626.sol

    • StashFactoryV2.sol

    • PoolManagerSecondaryProxy.sol

    • VoterProxy.sol

    • Interfaces.sol

    • TokenFactory.sol

    • PoolManagerProxy.sol

    • CrvDepositor.sol

    • Booster.sol

    • ConvexMasterChef.sol

    • BoosterOwner.sol

    • RewardHook.sol

    • PoolManagerV3.sol

    • ArbitartorVault.sol

Commit ID: b67d5b7d7fb87455533b5376e7c20157a6fc4e8c

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

2

Informational

4

Impact x Likelihood

HAL-01

HAL-03

HAL-04

HAL-05

HAL-06

HAL-02

Security analysisRisk levelRemediation Date
LACK OF TRANSFEROWNERSHIP PATTERNLowRisk Accepted
DUPLICATE ENTRY IN THE VESTING DISTRIBUTION LISTLowRisk Accepted
MISTAKENLY SENT ERC20 TOKENS CAN NOT RESCUED IN THE CONTRACTSInformationalAcknowledged
USING POSTFIX OPERATORS IN LOOPSInformationalAcknowledged
ARRAY.LENGTH USED IN LOOP CONDITIONSInformationalAcknowledged
USING != 0 CONSUMES LESS GAS THAN > 0 IN UNSIGNED INTEGER VALIDATIONInformationalAcknowledged

8. Findings & Tech Details

8.1 LACK OF TRANSFEROWNERSHIP PATTERN

// Low

Description

The current ownership transfer process for the Aura contracts inheriting from Ownable involves the current owner calling the transferOwnership() function:

Ownable.sol

    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

If the nominated account is not a valid account, it is entirely possible that the owner may accidentally transfer ownership to an uncontrolled account, losing the access to all functions with the onlyOwner modifier. For example, in the case of the AuraLocker contract, if a not valid account was assigned as a owner, administrative functions such as recovering LP rewards from other systems or shutting down the contract will not be possible.

This issue also applies to other types of privilege transfer methods, like the setAdmin function in the AuraVestedEscrow contract:

AuraVestedEscrow.sol

    function setAdmin(address _admin) external {
        require(msg.sender == admin, "!auth");
        admin = _admin;
    }

Affected Contracts:

  • aura-contracts/AuraClaimZap.sol
  • aura-contracts/AuraLocker.sol
  • aura-contracts/AuraPenaltyForwarder.sol
  • aura-contracts/ExtraRewardsDistributor.sol
  • aura-contracts/AuraVestedEscrow.sol
  • convex-platform/Booster.sol
  • convex-platform/ConvexMasterChef.sol
Score
Impact: 3
Likelihood: 1
Recommendation

RISK ACCEPTED: The Aura Finance team accepted the risk of this finding and does not plan to correct it in the future in order to keep the difference between Aura and Convex as minimal as possible to aid in manual reviews and minimize the chance of introducing bugs.

8.2 DUPLICATE ENTRY IN THE VESTING DISTRIBUTION LIST

// Low

Description

0xcc6548f1b572968f9539d604ec9ff4b933c1be74 address accidentally appeared twice in the AURA vesting distribution list (tasks/deploy/mainnet-config.ts).

tasks/deploy/mainnet-config.ts

// 24 MONTHS - 8.45%
{
    period: ONE_WEEK.mul(104),
    recipients: [
        { address: "0xe3B6c287C1369C6A4fa8d4e857813695C52948EF", amount: simpleToExactAmount(0.275, 24) }, // Core team
        { address: "0x023320e0C9Ac45644c3305cE574360E901c7f582", amount: simpleToExactAmount(0.5, 24) }, // Core team
        { address: "0xB1f881f47baB744E7283851bC090bAA626df931d", amount: simpleToExactAmount(3.5, 24) }, // Core team
        { address: "0xE4b32828B558F17BcaF5efD52f0C067dba38833c", amount: simpleToExactAmount(0.45, 24) }, // Core team
        { address: "0xcc6548f1b572968f9539d604ec9ff4b933c1be74", amount: simpleToExactAmount(0.075, 24) }, // Core team
        { address: "0x51d63958a63a31eb4028917f049ce477c8dd07bb", amount: simpleToExactAmount(0.5, 24) }, // Core team
        { address: "0x3078c3b436511152d86675f9cbfd89ec1672f804", amount: simpleToExactAmount(0.3, 24) }, // Core team
        { address: "0x3000d9b2c0e6b9f97f30abe379eaaa8a85a04afc", amount: simpleToExactAmount(0.325, 24) }, // Core team
        { address: "0x3CBFFF3E75881c1619eaa82DC724BDEE6fF6ED19", amount: simpleToExactAmount(0.06, 24) }, // Core team
        { address: "0xaf3824e8401299B25C4D59a8a035Cf9312a3B454", amount: simpleToExactAmount(0.175, 24) }, // Core team
        { address: "0x738175DB2C999581f29163e6D4D3516Ad4aF8834", amount: simpleToExactAmount(0.125, 24) }, // Core team
        { address: "0x0d9A5678E73e5BbC0ee09FAF8e550B196c76fDad", amount: simpleToExactAmount(0.5, 24) }, // Core team
        { address: "0x285b7EEa81a5B66B62e7276a24c1e0F83F7409c1", amount: simpleToExactAmount(1.5, 24) }, // Core team
        { address: "0xbee5a45271cc66a5b0e9dc4164a4f9df196d94fa", amount: simpleToExactAmount(0.125, 24) }, // Core team
        { address: "0xcc6548f1b572968f9539d604ec9ff4b933c1be74", amount: simpleToExactAmount(0.04, 24) }, // Core team
    ],
},

Aura Finance used this list to fund recipients with AURA reward tokens:

scripts/deploySystem.ts

        const vestingAddr = vestingGroup.recipients.map(m => m.address);
        const vestingAmounts = vestingGroup.recipients.map(m => m.amount);
        tx = await vestedEscrow.fund(vestingAddr, vestingAmounts);
Score
Impact: 1
Likelihood: 3
Recommendation

RISK ACCEPTED: The Aura Finance team will correct this finding through the governance.

8.3 MISTAKENLY SENT ERC20 TOKENS CAN NOT RESCUED IN THE CONTRACTS

// Informational

Description

The contracts are missing functions to sweep/rescue accidental ERC-20 transfers. Accidentally, sent ERC-20 tokens will be locked in the contracts.

Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: The Aura Finance team acknowledged this finding and does not plan to fix it in the future to keep the difference between Aura and Convex as minimal as possible to aid in the manual reviews and minimize the chance of introducing bugs.

8.4 USING POSTFIX OPERATORS IN LOOPS

// Informational

Description

In the loops below, postfix (e.g. i++) operators were used to increment or decrement variable values. It is known that, in loops, using prefix operators (e.g. ++i) costs less gas per iteration than using postfix operators.

Code Location

aura-contracts/AuraClaimZap.sol

  • Line 134 for (uint256 i = 0; i < rewardContracts.length; i++) {
  • Line 138 for (uint256 i = 0; i < extraRewardContracts.length; i++) {
  • Line 142 for (uint256 i = 0; i < tokenRewardContracts.length; i++) {

aura-contracts/AuraLocker.sol

  • Line 176 for (uint256 i = 0; i < rewardTokensLength; i++) {
  • Line 332 for (uint256 i; i < rewardTokensLength; i++) {
  • Line 350 for (uint256 i; i < rewardTokensLength; i++) {
  • Line 450 for (uint256 i = nextUnlockIndex; i < length; i++) {
  • Line 466 nextUnlockIndex++;
  • Line 537 i--;;

aura-contracts/AuraVestedEscrow.sol

  • Line 105 for (uint256 i = 0; i < _recipient.length; i++) {

aura-contracts/BalLiquidityProvider.sol

  • Line 52 for (uint256 i = 0; i < 2; i++) {

aura-contracts/ExtraRewardsDistributor.sol

  • Line 242 for (uint256 i = epochIndex; i < tokenEpochs; i++) {

convex-platform/ArbitartorVault.sol

  • Line 49 for(uint256 i = 0; i < _toPids.length; i++){

convex-platform/BaseRewardPool.sol

  • Line 218 for(uint i=0; i < extraRewards.length; i++){
  • Line 234 for(uint i=0; i < extraRewards.length; i++){
  • Line 266 for(uint i=0; i < extraRewards.length; i++){
  • Line 300 for(uint i=0; i < extraRewards.length; i++){

convex-platform/Booster.sol

  • Line 380 for(uint i=0; i < poolInfo.length; i++){
  • Line 539 for(uint256 i = 0; i < _gauge.length; i++){

convex-platform/BoosterOwner.sol

  • Line 144 for(uint256 i = 0; i < poolCount; i++){

convex-platform/ExtraRewardStashV3.sol

  • Line 125 for(uint256 i = 0; i < maxRewards; i++){
  • Line 201 for(uint i=0; i < tCount; i++){

convex-platform/PoolManagerSecondaryProxy.sol

  • Line 69 for(uint i=0; i < usedList.length; i++){

It is also possible to further optimize loops by using unchecked loop index incrementing and decrementing.

For example, based on the following test contract:

GasTestIncrement.sol

//SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

contract GasTestIncrement {
    function postiincrement(uint256 iterations) public {
        for (uint256 i = 0; i < iterations; i++) {
        }
    }
    function preiincrement(uint256 iterations) public {
        for (uint256 i = 0; i < iterations; ++i) {
        }
    }
    function uncheckedpreiincrement(uint256 iterations) public {
        for (uint256 i = 0; i < iterations;) {
            unchecked { ++i; }
        }
    }
}

We can see the difference in gas costs:

posti_prei_unchecked.png
Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: The Aura Finance team acknowledged this finding and does not plan to correct it in the future in order to keep the difference between Aura and Convex as minimal as possible to aid in manual reviews and minimize the chance of introducing bugs.

8.5 ARRAY.LENGTH USED IN LOOP CONDITIONS

// Informational

Description

In the loops below, unnecessary reading of the lengths of arrays on each iteration wastes gas.

Code Location

aura-contracts/AuraClaimZap.sol

  • Line 134 for (uint256 i = 0; i < rewardContracts.length; i++) {
  • Line 138 for (uint256 i = 0; i < extraRewardContracts.length; i++) {
  • Line 142 for (uint256 i = 0; i < tokenRewardContracts.length; i++) {

aura-contracts/AuraVestedEscrow.sol

  • Line 105 for (uint256 i = 0; i < _recipient.length; i++) {

convex-platform/ArbitartorVault.sol

  • Line 49 for(uint256 i = 0; i < _toPids.length; i++){

convex-platform/BaseRewardPool.sol

  • Line 218 for(uint i=0; i < extraRewards.length; i++){
  • Line 234 for(uint i=0; i < extraRewards.length; i++){
  • Line 266 for(uint i=0; i < extraRewards.length; i++){
  • Line 300 for(uint i=0; i < extraRewards.length; i++){

convex-platform/Booster.sol

  • Line 380 for(uint i=0; i < poolInfo.length; i++){
  • Line 539 for(uint256 i = 0; i < _gauge.length; i++){

convex-platform/PoolManagerSecondaryProxy.sol

  • Line 69 for(uint i=0; i < usedList.length; i++){

For example, based on the following test contract:

GasTestLength.sol

//SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

contract GasTestLength {

    uint256[] private arr = [0,1,2,3,4,5,6,7,8,9];

    function unoptimalized() public {
        for (uint256 i = 0; i < arr.length; ++i) {
        }

    }
    function optimalized() public {
        uint256 length = arr.length;
        for (uint256 i = 0; i < length; ++i) {
        }
    }
}

We can see the difference in gas costs:

arr_length.png
Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: The Aura Finance team acknowledged this finding and does not plan to correct it in the future in order to keep the difference between Aura and Convex as minimal as possible to aid in manual reviews and minimize the chance of introducing bugs.

8.6 USING != 0 CONSUMES LESS GAS THAN > 0 IN UNSIGNED INTEGER VALIDATION

// Informational

Description

In the require statements below, > 0 was used to validate if the unsigned integer parameters are bigger than 0. It is known that, using != 0 costs less gas than > 0.

Code Location

aura-contracts/AuraBalRewardPool.sol

  • Line 121 require(_amount > 0, "RewardPool : Cannot stake 0");
  • Line 139 require(_amount > 0, "RewardPool : Cannot stake 0");
  • Line 157 require(amount > 0, "RewardPool : Cannot withdraw 0");
  • Line 232 require(rewardsAvailable > 0, "!balance");

aura-contracts/AuraLocker.sol

  • Line 236 require(rewardData[_rewardsToken].lastUpdateTime > 0, ...
  • Line 285 require(_amount > 0, "Cannot stake 0");
  • Line 399 require(amt > 0, "Nothing locked");
  • Line 425 require(length > 0, "no locks");
  • Line 471 require(locked > 0, "no exp locks");
  • Line 511 require(len > 0, "Nothing to delegate");
  • Line 862 require(_rewards > 0, "No reward");

aura-contracts/AuraMerkleDrop.sol

  • Line 139 require(_amount > 0, "!amount");

aura-contracts/AuraPenaltyForwarder.sol

  • Line 55 require(bal > 0, "!empty");

aura-contracts/AuraVestedEscrow.sol

  • Line 55 require(totalLocked[_recipient] > 0, "!funding");

aura-contracts/BalLiquidityProvider.sol

  • Line 74 require(balAfter > 0, "!mint");

aura-contracts/ExtraRewardsDistributor.sol

  • Line 104 require(_amount > 0, "!amount");
  • Line 180 require(_index > 0 && ...);

aura-contracts/RewardPoolDepositWrapper.sol

  • Line 51 `require(minted > 0, "!mint");``

convex-platform/BaseRewardPool.sol

  • Line 215 require(_amount > 0, 'RewardPool : Cannot stake 0');
  • Line 231 require(amount > 0, 'RewardPool : Cannot withdraw 0');

convex-platform/ConvexMasterChef.sol

  • Line 138 require(totalAllocPoint > 0, "!alloc");

convex-platform/CrvDepositor.sol

  • Line 169 require(_amount > 0,"!>0");

convex-platform/PoolManagerSecondaryProxy.sol

  • Line 104 require(weight > 0, "must have weight");

convex-platform/interfaces/BoringMath.sol

  • Line 20 require(b > 0, "BoringMath: division by zero");
  • Line 102 require(b > 0, "BoringMath: division by zero");
  • Line 123 require(b > 0, "BoringMath: division by zero");
  • Line 143 require(b > 0, "BoringMath: division by zero");

For example, based on the following test contract:

GasTestRequire.sol

//SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

contract GasTestRequire {
    function originalrequire(uint256 len) public {
        require(len > 0, "Error!");
    }
    function optimalizedrequire(uint256 len) public {
        require(len != 0, "Error!");
    }
}

We can see the difference in gas costs:

require.png
Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: The Aura Finance team acknowledged this finding and does not plan to correct it in the future to keep the difference between Aura and Convex as minimal as possible to aid in manual reviews and minimize the chance of introducing bugs.

9. Automated Testing

STATIC ANALYSIS REPORT

Description

Halborn used automated testing techniques to enhance the coverage of certain areas of the scoped contracts. 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, Slither was run on the all-scoped 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.

Slither results

Note that due to the significant number of contracts, the low-risk findings displayed by Slither were not included in the report. However, we examined them individually during our audit.

aura-contracts/AuraLocker.sol

aura-contracts/AuraVestedEscrow.sol

aura-contracts/BalInvestor.sol

aura-contracts/CrvDepositorWrapper.sol

aura-contracts/RewardPoolDepositWrapper.sol

convex-platform/BaseRewardPool.sol

convex-platform/BaseRewardPool4626.sol

convex-platform/Booster.sol

convex-platform/ConvexMasterChef.sol

convex-platform/CrvDepositor.sol

convex-platform/ExtraRewardStashV3.sol

convex-platform/PoolManagerSecondaryProxy.sol

convex-platform/PoolManagerV3.sol

convex-platform/VoteProxy.sol

  • No major issues were found by Slither.

  • All the reentrancy vulnerabilities were checked individually, and they are all false positives.

  • The multiplications on the result of divisions are intentional or have minimal impact.

  • Unchecked transfers were correctly flagged by Sither, although it makes no sense to check the return value in this case, as any failed transfer would revert directly.

AUTOMATED SECURITY SCAN

Description

Halborn used automated security scanners to assist with detection of well-known security issues, and to identify low-hanging fruits on the targets for this engagement. Among the tools used was MythX, a security analysis service for Ethereum smart contracts. MythX performed a scan on all the contracts and sent the compiled results to the analyzers to locate any vulnerabilities.

MythX results

aura-contracts/AuraBalRewardPool.sol

aura-contracts/AuraMath.sol

aura-contracts/AuraMerkleDrop.sol

aura-contracts/AuraStakingProxy.sol

aura-contracts/AuraVestedEscrow.sol

aura-contracts/BalInvestor.sol

aura-contracts/RewardPoolDepositWrapper.sol

convex-platform/BaseRewardPool.sol

convex-platform/ConvexMasterChef.sol

convex-platform/PoolManagerV3.sol

convex-platform/ProxyFactory.sol

convex-platform/VoterProxy.sol

  • No major issues were found by MythX.

  • The requirement violations and assert violations are all false positives.

  • Integer Overflows and Underflows flagged by MythX are false positives.

  • block.number is not used as a source of randomness in any of the smart contracts.

  • DoS with Failed Call was correctly flagged by MythX, although the likelihood is minimal.

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.