Halborn Logo

AllocationVester Contract - Pangolin


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: March 20th, 2022 - March 30th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

5

Critical

0

High

0

Medium

0

Low

3

Informational

2


1. INTRODUCTION

Pangolin engaged Halborn to conduct a security audit on their AllocationVester smart contract beginning on March 20th, 2022 and ending on March 30th, 2022. The security assessment was scoped to the AllocationVester smart contract provided in the exchange-contracts GitHub repository pangolindex/exchange-contracts.

2. AUDIT SUMMARY

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

The purpose of this audit to achieve the following:

    • Ensure that all functions in the AllocationVester smart contract are intended.

    • Identify potential security issues in the AllocationVester smart contract.

In summary, Halborn identified few security risks that were mostly addressed by the Pangolin 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 bridge code 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 (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 contract:

    • AllocationVester.sol

Commit ID: 404631bb50e9d04935dd79930e9769ff73764bfd

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

2

Impact x Likelihood

HAL-01

HAL-02

HAL-03

HAL-04

HAL-05

Security analysisRisk levelRemediation Date
DOS WITH BLOCK GAS LIMITLowSolved - 03/29/2022
INACCURATE REWARD RATE CALCULATIONLowSolved - 03/29/2022
FLOATING PRAGMALowFuture Release
MISSING EVENTS EMITTINGInformationalSolved - 03/29/2022
MISSING ZERO ADDRESS CHECKInformationalSolved - 03/29/2022

8. Findings & Tech Details

8.1 DOS WITH BLOCK GAS LIMIT

// Low

Description

The setAllocations() function is used to set the token allocations for each recipient included in the function call:

setAllocations() function

function setAllocations(
    address[] memory accounts,
    uint[] memory allocations,
    uint[] memory durations
) external onlyOwner {
    uint length = accounts.length;
    require(length != 0, "empty array");
    require(
        length == allocations.length && length == durations.length,
        "varying-length arrays"
    );

    uint balance = token.balanceOf(address(this));
    for (uint i; i < length; ++i) {
        address account = accounts[i];
        uint allocation = allocations[i];
        uint duration = durations[i];
        Member storage member = members[account];

        require(account != address(0), "bad recipient");

        // check the member's remaining harvest
        if (member.reserve != 0) {
            // stash pending rewards of the member so it remains claimable
            member.stash = pendingHarvest(account);
            // free non-stashed reserves of the member from the reserves
            reserve -= (member.reserve - member.stash);
            // free non-stashed tokens from member's reserves
            member.reserve = member.stash;
        }

        // check the member's new allocation
        if (allocation != 0) {
            require(duration >= MIN_DURATION, "short vesting duration");

            // lock tokens as reserve and ensure sufficient balance
            reserve += allocation;
            require(balance >= reserve, "low balance");

            // add vesting info for the member
            member.reserve += allocation;
            member.rate = allocation / duration;
            member.lastUpdate = block.timestamp;

            // add the member to the set
            _membersAddresses.add(account);
        }

        emit AllocationSet(account, allocation, duration);
    }
}

\color{black} \color{white}

Since the length of recipients is not limited, in case there are too many recipients, the block gas limit could be reached, causing miners to not respond to all setAllocations() calls, thus blocking the main purpose of the smart contract.

Score
Impact: 3
Likelihood: 1
Recommendation

**SOLVED: ** The Pangolin Team solved this issue by implementing the above recommendation. The maximum number of recipients has been set to 40, preventing gas usage from increasing too much.

Commit ID: 9563309e6aaf1d0fc930a94cb0fc903d7017d40a

8.2 INACCURATE REWARD RATE CALCULATION

// Low

Description

AllocationVester.sol contract calculates the reward unlock rate by dividing the total amount of tokens to be unlocked by the total vesting period (in seconds), which can go from eight weeks (4838400 seconds) to infinity. However, when the amount of tokens is a number of the same order as the total vesting period, rounding Solidity to zero will introduce inaccuracies in the result of the uint division:

The accuracy of the reward unlock rate depends primarly on the ERC20 token to be distributed, which is associated with the AllocationVester contract during its deployment. The use of the token contracts with high decimal values such as Png (18) is considered safe, but the use of contracts with lower decimal values such as USDT (6) may result in an inaccurate rate calculation, which can undermine user trust.

For example, allocating 30 USDT for one year would result in an unlock rate of 0, and it would take 5% more time to unlock 1000 USDT allocated for two years.

However, it has been noted that incorrectly set allocations can be easily overridden by the contract owner, if needed, causing locked funds to be returned to the contract reserve.

Code Location

Reward rate calculation

// add vesting info for the member
member.reserve += allocation;
member.rate = allocation / duration;
member.lastUpdate = block.timestamp;
Score
Impact: 3
Likelihood: 1
Recommendation

**SOLVED: ** The Pangolin Team solved this issue by multiplying the token allocation by a coefficient that will add 11 decimals of precision when calculating the reward unlock rate, which is considered enough for this contract.

Commit ID: 69fa24b3b55c0c7b5bc566354e9b1b36eb3f6272

8.3 FLOATING PRAGMA

// Low

Description

AllocationVester.sol contract uses the floating pragma ^0.8.0. The contract 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 new which has not been extensively tested.

Code Location

Floating pragma

pragma solidity ^0.8.0;
Score
Impact: 3
Likelihood: 1
Recommendation

**PENDING: ** The Pangolin Team acknowledged this issue and will address it in future releases.

8.4 MISSING EVENTS EMITTING

// Informational

Description

It has been observed that important functionalities are missing emitting events. The following functions should emit events in the AllocationVester contract:

  • withdraw()
  • harvest()
Score
Impact: 2
Likelihood: 1
Recommendation

**SOLVED: ** The Pangolin Team solved this issue by defining new events that will be emitted every time a reward pickup or withdrawal occurs.

Commit ID: d8137ff12bfbf6c083939b8d487e6dd47a70a3ea

8.5 MISSING ZERO ADDRESS CHECK

// Informational

Description

The constructor of the AllocationVester contract is missing address validation. The distributionToken parameter should be checked to be non-zero. This is considered a best practice.

Code Location

Missing zero address check

    constructor(IERC20 distributionToken) {
        token = distributionToken;
    }
Score
Impact: 1
Likelihood: 1
Recommendation

**SOLVED: ** The Pangolin Team solved this issue by implementing a zero check address in the contract constructor.

Commit ID: 2a5bb1afe0e1cb0aab67b9f8e75970a3ad75c992

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.

Slither Results

slither.png
  • No major issues found by Slither.

AUTOMATED SECURITY SCAN

MYTHX

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

Results

SphrVesting.sol

  • No major issues found by MythX.

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.