Halborn Logo

LMCV part 3 - DAMfinance


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: October 27th, 2022 - November 17th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

8

Critical

1

High

1

Medium

2

Low

2

Informational

2


1. INTRODUCTION

DAMfinance engaged Halborn to conduct a security audit on their smart contracts beginning on 2022-10-27 and ending on 2022-11-17. The security assessment was scoped to the smart contracts in the TO_AUDIT folder provided in the audit3Quote branch of the DecentralizedAssetManagement/lmcv GitHub repository.

The security audit also included the new modifications of the dPrime token that allowed delayed minting in case of cross-chain token transfers. The modified contract was provided in the audit3WithBlockDelay branch of the DecentralizedAssetManagement/lmcv GitHub repository.

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 security risks that were mostly addressed by the DAMfinance 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 walk-through

    • 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

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

    • Local deployment (Hardhat, Remix IDE, Brownie)

4. SCOPE

The security assessment was scoped to the following smart contracts:

    • lmcv/contracts/TO_AUDIT/LayerZero/IOFT.sol

    • lmcv/contracts/TO_AUDIT/LayerZero/dPrimeConnectorLZ.sol

    • lmcv/contracts/TO_AUDIT/dependencies/AuthAdmin.sol

    • lmcv/contracts/TO_AUDIT/hyperlane/dPrimeConnectorHyperlane.sol

    • lmcv/contracts/TO_AUDIT/dPrime.sol

    • lmcv/contracts/TO_AUDIT/dPrimeGuardian.sol

The security audit also included the new modifications of the dPrime token:

    • lmcv/contracts/TO_AUDIT/LayerZero/dPrimeConnectorLZ.sol

    • lmcv/contracts/TO_AUDIT/hyperlane/dPrimeConnectorHyperlane.sol

    • lmcv/contracts/TO_AUDIT/dPrime.sol

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

1

Medium

2

Low

2

Informational

2

Impact x Likelihood

HAL-01

HAL-04

HAL-02

HAL-06

HAL-03

HAL-05

HAL-07

HAL-08

Security analysisRisk levelRemediation Date
UNLIMITED MINTING BY REUSING FAILED HYPERLANE MESSAGESCriticalSolved - 11/11/2022
MISMATCHING DATA LOCATION DURING INHERITANCEHighSolved - 11/17/2022
DENIAL OF SERVICE USING MINT DELAYMediumSolved - 11/15/2022
INCOMPLETE GUARDIAN IMPLEMENTATIONMediumSolved - 11/11/2022
MISSING EVENTS FOR RELEVANT OPERATIONSLowSolved - 11/11/2022
MISSING ZERO VALUE CHECKSLowSolved - 11/11/2022
REVERT STRING SIZE OPTIMIZATIONInformationalAcknowledged
STATE VARIABLES MISSING IMMUTABLE MODIFIERInformationalSolved - 11/16/2022

8. Findings & Tech Details

8.1 UNLIMITED MINTING BY REUSING FAILED HYPERLANE MESSAGES

// Critical

Description

It is possible to transfer dPrime tokens across chains using the dPrimeConnectorHyperlane contract. If minting the tokens on the destination chain fails, then the program stores the failed transaction in a mapping. It is possible to manually retry the failed transactions by calling the retry function on the destination chain. The function reads the failed transaction details from a mapping and mints the specified tokens for the receiver. However, the function does not update the mapping after a successfully retried transaction, so it is possible to mint as many tokens as desired by calling the retry function multiple times with the data of the failed transaction.

Code Location

hyperlane/dPrimeConnectorHyperlane.sol

function retry(uint32 _origin, address _recipient, uint256 _nonce) external {
    uint256 amount = failedMessages[_origin][_recipient][_nonce];

    try dPrimeLike(dPrimeContract).mint(_recipient, amount) {
        emit ReceivedTransferRemote(_origin, _recipient, amount);
    } catch {
        emit FailedTransferRemote(_origin, _recipient, nonce, amount);
    }
}

As proof of concept, a failed Hyperlane message was created in a local test environment. Then the same failed message was used multiple times to mint dPrime tokens for the receiver.

unlimited_minting.png

It is noted that it is possible to mint as many tokens as desired using the failed message.

Score
Impact: 5
Likelihood: 5
Recommendation

SOLVED: The DAMfinance team solved the issue in commit cfc13a8 by deleting the successfully retried messages from the failedMessages mapping.

8.2 MISMATCHING DATA LOCATION DURING INHERITANCE

// High

Description

It was identified that the dPrimeConnectorHyperlane contract was incompatible with the latest Hyperlane protocol and might not work with new versions of Hyperlane Inbox contracts, which can lead to users losing their dPrime tokens during cross-chain transfers.

The dPrimeConnectorHyperlane contract is inherited from the Hyperlane Router abstract contract. When receiving a message from another chain, the handle function is called by the corresponding Hyperlane Inbox contract. This function calls the internal _handle function that can be overridden in the derived contracts.

The following code is an example from the Hyperlane Router version 0.5.0:

@hyperlane-xyz/app/contracts/Router.sol (0.5.0)

    function handle(
        uint32 _origin,
        bytes32 _sender,
        bytes memory _message
    ) external virtual override onlyInbox onlyRemoteRouter(_origin, _sender) {
        // TODO: callbacks on success/failure
        _handle(_origin, _sender, _message);
    }

    // ============ Virtual functions ============
    function _handle(
        uint32 _origin,
        bytes32 _sender,
        bytes memory _message
    ) internal virtual;

The _handle function was overridden in the dPrimeConnectorHyperlane contract:

hyperlane/dPrimeConnectorHyperlane.sol

    function _handle(
        uint32 _origin,
        bytes32,
        bytes memory _message
    ) internal override alive {

However, from Hyperlane version 0.5.1, the storage location of the _message parameter was changed from memory to calldata:

@hyperlane-xyz/app/contracts/Router.sol (0.5.1)

    function handle(
        uint32 _origin,
        bytes32 _sender,
        bytes calldata _message
    ) external virtual override onlyInbox onlyRemoteRouter(_origin, _sender) {
        // TODO: callbacks on success/failure
        _handle(_origin, _sender, _message);
    }

    // ============ Virtual functions ============
    function _handle(
        uint32 _origin,
        bytes32 _sender,
        bytes calldata _message
    ) internal virtual;

A bug concerning data location during inheritance was identified in Solidity on May 17, 2022. According to the Solidity team, the calldata pointer in these cases is interpreted as a memory pointer which results in reading invalid data from memory.

It is also noted that the current protocol is not configured with a fixed version of Hyperlane dependencies. In the package.json configuration file, floating versions are used, which means that the contract might be deployed with an incompatible Hyperlane messaging protocol:

package.json

  "dependencies": {
    "@chainlink/contracts": "^0.4.1",
    "@hyperlane-xyz/app": "^0.5.0",
    "@hyperlane-xyz/sdk": "^0.5.0",
    "@layerzerolabs/solidity-examples": "^0.0.4",
    "@openzeppelin/contracts": "^4.5.0",
    "solc": "^0.8.15"
  }

As proof of concept, the dPrimeConnectorHyperlane contract was deployed with Hyperlane version 0.5.0. It was possible to mint tokens by calling the handle function:

missmatching_data_locations_2_memory.png

The contract was also deployed using the Hyperlane version 0.5.1. Calling the handle function with the same parameters resulted in an error and corrupted data:

missmatching_data_locations_2_calldata.png

Note that it is not possible to recover the tokens from corrupted data using the retry function of the dPrimeConnectorHyperlane contract.

Score
Impact: 4
Likelihood: 4
Recommendation

SOLVED: The DAMfinance team solved the issue in commit c19b217 by changing the storage location of the _message parameter from memory to calldata and fixing the versions of the Hyperlane contracts in the package.json configuration file.

8.3 DENIAL OF SERVICE USING MINT DELAY

// Medium

Description

The new version of the dPrime token contract was extended with an additional security feature that prevents transferring funds for a certain number of blocks after a cross-chain transfer. The delay allows the DAMfinance team team to pause the contract in case the cross-chain messaging is compromised to limit the impact of such security incidents.

However, a malicious actor can also exploit this behavior to manipulate the market during volatile times or perform a griefing attack by continuously sending tokens to the targeted users. The likelihood of such attacks is increased because it is enough to send only one token to prevent the transfer of the targeted user's funds temporarily.

Code Location

dPrime.sol

    function mintAndDelay(address to, uint256 value) external auth {
        transferBlockRelease[to] = block.number + transferBlockWait;
        _mint(to, value);
    }

dPrime.sol

    function transferFrom(address from, address to, uint256 value) external alive returns (bool) {
        require(to != address(0) && to != address(this), "dPrime/invalid-address");
        require(block.number > transferBlockRelease[from], "dPrime/transfer too soon after cross-chain mint");
Score
Impact: 3
Likelihood: 3
Recommendation

SOLVED: The DAMfinance team solved the issue in commit e76f394 by adding a configurable threshold to the mintAndDelay function.

8.4 INCOMPLETE GUARDIAN IMPLEMENTATION

// Medium

Description

It was identified that in the dPrimeGuardian contract, it is not possible to add any values to the pipeAddresses mapping. And therefore, in case of an incident, the dPrimeGuardian contract cannot be used to take back the admin privileges of any insecure connector from the dPrime token because the removeConnectorAdmin reads the required addresses from the mapping.

Code Location

dPrimeGuardian.sol

function removeConnectorAdmin(bytes32 pipeName) external auth {
    dPrimeLike(dPrimeContract).deny(pipeAddresses[pipeName]);
    emit HaltedPipe(pipeName);
}
Score
Impact: 4
Likelihood: 2
Recommendation

SOLVED: The DAMfinance team solved the issue in commit cfc13a8 by adding the setPipeAddress function to the dPrimeGuardian contract.

8.5 MISSING EVENTS FOR RELEVANT OPERATIONS

// Low

Description

The connector and guardian contracts are inherited from the AuthAdmin contract. It was identified that in this contract, the cage function did not emit any events. As a result, blockchain monitoring systems might not be able to timely detect suspicious behaviors related to the cage function.

Note that the Cage event declared in the dPrimeConnectorHyperlane contract is not used and could be removed from the contract.

Score
Impact: 2
Likelihood: 2
Recommendation

SOLVED: The DAMfinance team solved the issue in commit cfc13a8 by emitting the event in the cage function of the AuthAdmin contract and removing the redundant Cage event from the dPrimeConnectorHyperlane contract.

8.6 MISSING ZERO VALUE CHECKS

// Low

Description

It was identified that within the code, several functions were lacking zero address or zero value validation on important parameters. Setting invalid parameters in the examples below might result in loss of funds, waste of gas, lose of administrative controls, or reverting during important operations:

Missing zero address checks:

LayerZero/dPrimeConnectorLZ.sol:

  • Line 24 constructor is missing zero-address checks for _lzEndpoint, _dPrimeContract.

hyperlane/dPrimeConnectorHyperlane.sol:

  • Line 71 initialize is missing zero-address checks for _abacusConnectionManager, _interchainGasPaymaster, dPrimeContract.
  • Line 95 transferRemote is missing zero-address check for _recipient.

dPrimeGuardian.sol

  • Line 16: constructor is missing zero-address check for _dPrimeContract.

It is also recommended to validate the following parameters and variables:

hyperlane/dPrimeConnectorHyperlane.sol:

  • Line 95 transferRemote is missing zero value checks for _destination, _amount.
  • Line 142 retry is missing zero-value check for amount.
Score
Impact: 3
Likelihood: 1
Recommendation

SOLVED: The DAMfinance team solved the issue in commit cfc13a8 by adding checks where appropriate.

8.7 REVERT STRING SIZE OPTIMIZATION

// Informational

Description

Shortening the revert strings to fit within 32 bytes will decrease deployment time gas and reduce runtime gas when the revert condition is met.

Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead to calculate memory offset, etc.

Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: The DAMfinance team acknowledged this finding.

8.8 STATE VARIABLES MISSING IMMUTABLE MODIFIER

// Informational

Description

State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time.

The following state variables are missing the immutable modifier:

dPrimeGuardian.sol

  • Line 16: address public dPrimeContract;

dPrimeConnectorLZ.sol

  • Line 19: address public dPrimeContract;
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: The DAMfinance team solved the issue in commits e1a2d28 and cfc13a8 by adding the immutable modifier to the state variables.

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

LayerZero/dPrimeConnectorLZ.sol

hyperlane/dPrimeConnectorHyperlane.sol

dPrime.sol

dPrimeGuardian.sol

  • No major issues were found by Slither.

  • The reentrancy vulnerabilities are all false positives.

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

dependencies/AuthAdmin.sol

hyperlane/dPrimeConnectorHyperlane.sol

dPrimeGuardian.sol

  • No major issues were found by MythX.

  • The reentrancy vulnerabilities and requirement violations are all false positives.

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.