Prepared by:
HALBORN
Last Updated 04/26/2024
Date of Engagement by: October 27th, 2022 - November 17th, 2022
100% of all REPORTED Findings have been addressed
All findings
8
Critical
1
High
1
Medium
2
Low
2
Informational
2
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.
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.
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
)
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
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 analysis | Risk level | Remediation Date |
---|---|---|
UNLIMITED MINTING BY REUSING FAILED HYPERLANE MESSAGES | Critical | Solved - 11/11/2022 |
MISMATCHING DATA LOCATION DURING INHERITANCE | High | Solved - 11/17/2022 |
DENIAL OF SERVICE USING MINT DELAY | Medium | Solved - 11/15/2022 |
INCOMPLETE GUARDIAN IMPLEMENTATION | Medium | Solved - 11/11/2022 |
MISSING EVENTS FOR RELEVANT OPERATIONS | Low | Solved - 11/11/2022 |
MISSING ZERO VALUE CHECKS | Low | Solved - 11/11/2022 |
REVERT STRING SIZE OPTIMIZATION | Informational | Acknowledged |
STATE VARIABLES MISSING IMMUTABLE MODIFIER | Informational | Solved - 11/16/2022 |
// Critical
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.
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.
It is noted that it is possible to mint as many tokens as desired using the failed message.
SOLVED: The DAMfinance team
solved the issue in commit cfc13a8 by deleting the successfully retried messages from the failedMessages
mapping.
// High
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
:
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:
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
:
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:
"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:
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:
Note that it is not possible to recover the tokens from corrupted data using the retry
function of the dPrimeConnectorHyperlane
contract.
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.
// Medium
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.
function mintAndDelay(address to, uint256 value) external auth {
transferBlockRelease[to] = block.number + transferBlockWait;
_mint(to, value);
}
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");
SOLVED: The DAMfinance team
solved the issue in commit e76f394 by adding a configurable threshold to the mintAndDelay
function.
// Medium
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.
function removeConnectorAdmin(bytes32 pipeName) external auth {
dPrimeLike(dPrimeContract).deny(pipeAddresses[pipeName]);
emit HaltedPipe(pipeName);
}
SOLVED: The DAMfinance team
solved the issue in commit cfc13a8 by adding the setPipeAddress
function to the dPrimeGuardian
contract.
// Low
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.
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.
// Low
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
:
constructor
is missing zero-address checks for _lzEndpoint
, _dPrimeContract
.hyperlane/dPrimeConnectorHyperlane.sol
:
initialize
is missing zero-address checks for _abacusConnectionManager
, _interchainGasPaymaster
, dPrimeContract
.transferRemote
is missing zero-address check for _recipient
.dPrimeGuardian.sol
constructor
is missing zero-address check for _dPrimeContract
.It is also recommended to validate the following parameters and variables:
hyperlane/dPrimeConnectorHyperlane.sol
:
transferRemote
is missing zero value checks for _destination
, _amount
.retry
is missing zero-value check for amount
.SOLVED: The DAMfinance team
solved the issue in commit cfc13a8 by adding checks where appropriate.
// Informational
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.
ACKNOWLEDGED: The DAMfinance team
acknowledged this finding.
// Informational
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
address public dPrimeContract;
dPrimeConnectorLZ.sol
address public dPrimeContract;
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.
LayerZero/dPrimeConnectorLZ.sol
hyperlane/dPrimeConnectorHyperlane.sol
dPrime.sol
dPrimeGuardian.sol
No major issues were found by Slither.
The reentrancy vulnerabilities are all false positives.
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.
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.
// Download the full report
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed