Halborn Logo

NewOrderDAO


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: January 17th, 2022 - January 24th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

6

Critical

1

High

0

Medium

0

Low

2

Informational

3


1. INTRODUCTION

NewOrderDAO engaged Halborn to conduct a security audit on their fee collector smart contract beginning on January 17th, 2022 and ending on January 24th, 2022. The security assessment was scoped to the smart contracts provided in the following GitHub repositories:

2. AUDIT SUMMARY

The team at Halborn was provided a week for the engagement and assigned a full-time security engineer to audit the smart contracts. 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 addressed by NewOrderDAO 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 contracts:

GovernanceTokenV2.sol Commit ID: e9cde694e53005e4504ae44d6462ee07e638a511 GovernanceTokenV2.sol Fixed Commit ID: 9a83d8dd7c02515ffd161b5356db90c0add5e8a0

DisbursementCliff.sol Commit ID: 4bc016a9daf9896c9bd602b132e2df70d8737c24 DisbursementCliff.sol Fixed Commit ID: d3c6d4a789dc370793a09a7d0d997c3cbf9fb073

one-way-swap.sol Commit ID: d2d2f724f3ae1652c138423bbe794a8ec3535b18 OneWaySwap.sol Fixed Commit ID: 12b93877647ad63bac85aaa65b2b4243f81392e8

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

1

High

0

Medium

0

Low

2

Informational

3

Impact x Likelihood

HAL-01

HAL-02

HAL-03

HAL-04

HAL-05

HAL-06

Security analysisRisk levelRemediation Date
OVERFLOW IN CALCMAXTRANSFERRABLE FUNCTIONCriticalSolved - 01/28/2022
UNCHECKED TRANSFERLowSolved - 01/28/2022
MISSING ZERO ADDRESS CHECKSLowSolved - 01/28/2022
SOLC 0.8.3 COMPILER VERSION CONTAINS MULTIPLE BUGSInformationalSolved - 01/28/2022
POSSIBLE MISUSE OF PUBLIC FUNCTIONSInformationalSolved - 01/28/2022
TIMELOCKTOKEN IS NOT PAUSABLEInformationalSolved - 01/28/2022

8. Findings & Tech Details

8.1 OVERFLOW IN CALCMAXTRANSFERRABLE FUNCTION

// Critical

Description

In the contract, TimeLockToken the function calcMaxTransferrable() is used to calculate the maximum amount of transferrable tokens for an address:

GovernanceTokenV2.sol

/// @dev Calculates the maximum amount of transferrable tokens for address `who`
/// @return Number of transferrable tokens 
function calcMaxTransferrable(address who)
    public
    view
    returns (uint256)
{
    if(timelockedTokens[who] == 0){
        return balanceOf(who);
    }
    uint256 maxTokens;
    if( vestTime[who] > block.timestamp || cliffTime[who] > block.timestamp){
        maxTokens = 0;
    } else {
        maxTokens = timelockedTokens[who] * (block.timestamp - vestTime[who]) / disbursementPeriod[who];
    }
    if (timelockedTokens[who] < maxTokens){
      return balanceOf(who);
    }
    return balanceOf(who) - timelockedTokens[who] + maxTokens;
}

This function is called with every transfer because of the _beforeTokenTransfer() hook:

GovernanceTokenV2.sol

function _beforeTokenTransfer(
    address from,
    address to,
    uint256 amount
) internal virtual override {
    uint maxTokens = calcMaxTransferrable(from);
    if (from != address(0x0) && amount > maxTokens){
      revert("amount exceeds available unlocked tokens");
    }
}

An overflow can occur in the return balanceOf(who) - timelockedTokens[who] + maxTokens; line that will not allow the user to transfer any of his tokens, even if they are unlocked, until the end of the disbursementPeriod

The Proof of Concept was executed using the following parameters:

  • timelockedTokens -> 1000_000000000000000000
  • vestTime -> chain.time() = now()
  • cliffTime -> chain.time() + 15768000 = 6 months
  • disbursementPeriod -> 31536000 seconds = 1 year

Then:

  1. Waited 6 months: chain.sleep(15768000).
  2. 6 months later 500_000000000000000000 of the user2 tokens were unlocked.
  3. 200_000000000000000000 tokens were successfully transferred from user2 to user3.
  4. user2 tried then to transfer another 200_000000000000000000 to user4. The transfer reverts with an Integer overflow error. All of the user2 tokens are totally locked now.
  5. After this, user2 has to wait until the end of the disbursementPeriod to be able to transfer his tokens.
2.png
Score
Impact: 5
Likelihood: 5
Recommendation

SOLVED: NewOrderDAO team solved this issue in the commit ID: e7547837502f1e48151a52acaaa5c722dca4c253:

3.png

8.2 UNCHECKED TRANSFER

// Low

Description

In the contracts DisbursementCliff and OneWaySwap the return value of some external transfer calls are not checked. Several tokens do not revert in case of failure and return false. If that happened, for example in the DisbursementCliff contract, the withdrawnTokens state variable would be incorrectly updated and the calculation of the amount of vested tokens would be wrong. It is also considered a best practice to check the return value of a ERC20.transfer() call.

Code Location

DisbursementCliff.sol

DisbursementCliff.sol

function withdraw(address _to, uint256 _value)
    public
    isReceiver
{
    uint maxTokens = calcMaxWithdraw();
    if (_value > maxTokens){
      revert("Withdraw amount exceeds allowed tokens");
    }
    withdrawnTokens += _value;
    token.transfer(_to, _value);
}

/// @dev Transfers all tokens to multisig wallet
function walletWithdraw()
    public
    isWallet
{
    uint balance = token.balanceOf(address(this));
    withdrawnTokens += balance;
    token.transfer(wallet, balance);
}

one-way-swap.sol

one-way-swap.sol

function swap(uint256 amount) 
    public
    whenNotPaused
{
    oldToken.transferFrom(msg.sender, burnAddress, amount);
    newToken.transfer(msg.sender, amount);
}

function burn(uint256 amount, string memory why)
    public
    whenNotPaused
{
    oldToken.transferFrom(msg.sender, burnAddress, amount);
    emit Burned(msg.sender, amount, why);
}

one-way-swap.sol

function walletWithdraw(ERC20 token, uint256 amount, address destination)
    public
    onlyOwner
{
    token.transfer(destination, amount);
}
Score
Impact: 4
Likelihood: 1
Recommendation

SOLVED: NewOrderDAO team now makes use of SafeERC20.safeTransfer() and SafeERC20.safeTransferFrom() in all their token transfers.

8.3 MISSING ZERO ADDRESS CHECKS

// Low

Description

The constructor of the OneWaySwap contract is missing address validation. Every address should be validated and checked that is different from zero. This is also considered a best practice.

one-way-swap.sol

constructor(ERC20 oldToken_, ERC20 newToken_, address burnAddress_)
{
    oldToken = oldToken_;
    newToken = newToken_;        
    burnAddress = burnAddress_;
    _pause();
}
Score
Impact: 2
Likelihood: 3
Recommendation

SOLVED: NewOrderDAO team added the zero address checks.

8.4 SOLC 0.8.3 COMPILER VERSION CONTAINS MULTIPLE BUGS

// Informational

Description

Solidity compiler version 0.8.3, 0.8.4 and 0.8.9 fixed important bugs in the compiler. The version 0.8.3 set in the truffle-config.js file of the GovernanceTokenV2 project is missing all these fixes:

Code Location

GovernanceTokenV2.sol

pragma solidity ^0.8.3;

truffle-config.js

compilers: {
  solc: {
    version: "0.8.3", // Fetch exact version from solc-bin (default: truffle's version)
    // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
    // settings: {          // See the solidity docs for advice about optimization and evmVersion
    //  optimizer: {
    //    enabled: false,
    //    runs: 200
    //  },
    //  evmVersion: "byzantium"
    // }
  },
},
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: NewOrderDAO team set in the truffle-config.js file the 0.8.9 version for the contracts GovernanceTokenV2 and OneWaySwap and the 0.6.12 version for the DisbursementCliff contract.

8.5 POSSIBLE MISUSE OF PUBLIC FUNCTIONS

// Informational

Description

In the following contracts there are functions marked as public but they are never directly called within the same contract or in any of their descendants:

GovernanceTokenV2.sol

  • newTimeLock() (GovernanceTokenV2.sol#70-85)
  • balanceUnlocked() (GovernanceTokenV2.sol#158-160)

DisbursementCliff.sol

  • withdraw() (DisbursementCliff.sol#67-77)
  • walletWithdraw() (DisbursementCliff.sol#80-87)

one-way-swap.sol

  • swap() (onewayswap.sol#32-38)
  • burn() (onewayswap.sol#40-46)
  • walletWithdraw() (onewayswap.sol#66-71)
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: NewOrderDAO team declared the mentioned functions as external to reduce the gas costs.

8.6 TIMELOCKTOKEN IS NOT PAUSABLE

// Informational

Description

The contract TimeLockToken is not pausable/ownable. Even if this addition would add centralization it could be useful in case of an emergency, for example, the token could be paused in case of a cross-chain bridge hack.

Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: NewOrderDAO team created a Pausable variant of the TimeLockToken contract called GovernanceTokenPausable. NewOrderDAO team will decide which variant to deploy.

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

GovernanceTokenV2.sol

DisbursementCliff.sol

one-way-swap.sol

  • No major issues were found by Slither.

ERC20 checks

GovernanceTokenV2.sol

  • All the Slither ERC20 checks were passed successfully.

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.