Prepared by:
HALBORN
Last Updated 04/26/2024
Date of Engagement by: March 20th, 2022 - March 30th, 2022
80% of all REPORTED Findings have been addressed
All findings
5
Critical
0
High
0
Medium
0
Low
3
Informational
2
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.
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
.
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
)
IN-SCOPE: The security assessment was scoped to the following smart contract:
AllocationVester.sol
Commit ID: 404631bb50e9d04935dd79930e9769ff73764bfd
Critical
0
High
0
Medium
0
Low
3
Informational
2
Impact x Likelihood
HAL-01
HAL-02
HAL-03
HAL-04
HAL-05
Security analysis | Risk level | Remediation Date |
---|---|---|
DOS WITH BLOCK GAS LIMIT | Low | Solved - 03/29/2022 |
INACCURATE REWARD RATE CALCULATION | Low | Solved - 03/29/2022 |
FLOATING PRAGMA | Low | Future Release |
MISSING EVENTS EMITTING | Informational | Solved - 03/29/2022 |
MISSING ZERO ADDRESS CHECK | Informational | Solved - 03/29/2022 |
// Low
The setAllocations()
function is used to set the token allocations for each recipient included in the function call:
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.
**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
// Low
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.
// add vesting info for the member
member.reserve += allocation;
member.rate = allocation / duration;
member.lastUpdate = block.timestamp;
**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
// Low
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.
pragma solidity ^0.8.0;
**PENDING: ** The Pangolin Team
acknowledged this issue and will address it in future releases.
// Informational
It has been observed that important functionalities are missing emitting events. The following functions should emit events in the AllocationVester
contract:
**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
// Informational
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.
constructor(IERC20 distributionToken) {
token = distributionToken;
}
**SOLVED: ** The Pangolin Team
solved this issue by implementing a zero check address in the contract constructor.
Commit ID:
2a5bb1afe0e1cb0aab67b9f8e75970a3ad75c992
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.
No major issues found by Slither.
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.
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.
// Download the full report
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed