Prepared by:
HALBORN
Last Updated 10/09/2024
Date of Engagement by: September 30th, 2024 - October 2nd, 2024
100% of all REPORTED Findings have been addressed
All findings
4
Critical
0
High
0
Medium
1
Low
0
Informational
3
Dynex
engaged Halborn to conduct a security assessment on smart contracts beginning on 09/30/2024 and ending on 10/02/2024. The security assessment was scoped to the smart contracts provided to the Halborn team.
The team at Halborn dedicated 3 working days for the engagement and assigned one full-time security engineer to evaluate 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 assessment is to:
Ensure that smart contract functions operate as intended.
Identify potential security issues with the smart contracts.
In summary, Halborn identified some minor improvements to reduce the likelihood and impact of risks, which were successfully addressed by the Dynex team
. The main ones were the following:
Potential amount restriction bypass.
Hard cap configuration in DNX token
Halborn performed a combination of manual, semi-automated and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of this assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of the 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 assessment:
Research into architecture and purpose.
Smart contract manual code review and walk-through.
Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any vulnerability classes
Manual testing by custom scripts.
Static Analysis of security for scoped contract, and imported functions. (Slither
)
Local deployment and testing ( Hardhat
)
EXPLOITABILIY METRIC () | METRIC VALUE | NUMERICAL VALUE |
---|---|---|
Attack Origin (AO) | Arbitrary (AO:A) Specific (AO:S) | 1 0.2 |
Attack Cost (AC) | Low (AC:L) Medium (AC:M) High (AC:H) | 1 0.67 0.33 |
Attack Complexity (AX) | Low (AX:L) Medium (AX:M) High (AX:H) | 1 0.67 0.33 |
IMPACT METRIC () | METRIC VALUE | NUMERICAL VALUE |
---|---|---|
Confidentiality (C) | None (I:N) Low (I:L) Medium (I:M) High (I:H) Critical (I:C) | 0 0.25 0.5 0.75 1 |
Integrity (I) | None (I:N) Low (I:L) Medium (I:M) High (I:H) Critical (I:C) | 0 0.25 0.5 0.75 1 |
Availability (A) | None (A:N) Low (A:L) Medium (A:M) High (A:H) Critical (A:C) | 0 0.25 0.5 0.75 1 |
Deposit (D) | None (D:N) Low (D:L) Medium (D:M) High (D:H) Critical (D:C) | 0 0.25 0.5 0.75 1 |
Yield (Y) | None (Y:N) Low (Y:L) Medium (Y:M) High (Y:H) Critical (Y:C) | 0 0.25 0.5 0.75 1 |
SEVERITY COEFFICIENT () | COEFFICIENT VALUE | NUMERICAL VALUE |
---|---|---|
Reversibility () | None (R:N) Partial (R:P) Full (R:F) | 1 0.5 0.25 |
Scope () | Changed (S:C) Unchanged (S:U) | 1.25 1 |
Severity | Score Value Range |
---|---|
Critical | 9 - 10 |
High | 7 - 8.9 |
Medium | 4.5 - 6.9 |
Low | 2 - 4.4 |
Informational | 0 - 1.9 |
Critical
0
High
0
Medium
1
Low
0
Informational
3
Security analysis | Risk level | Remediation Date |
---|---|---|
Incorrect HARD_CAP Configuration | Medium | Solved - 10/01/2024 |
Incorrect minSend Check Allows Zero-Amount Transfers | Informational | Solved - 10/01/2024 |
ERC20 Permit Functionality in sendTokensWithPermit | Informational | Solved - 10/02/2024 |
Potential Array Length Mismatch in receiveTokensBatch | Informational | Solved - 10/05/2024 |
// Medium
The contract defines the HARD_CAP as follows:
uint256 public constant HARD_CAP = 110_000_000 ether; // <= 110_000_000e18
This configuration assumes 18 decimal places, which is standard for many ERC20 tokens.
However, the contract explicitly defines 9 decimal places for the DNX token:
function decimals() public view override returns (uint8) {
return 9;
}
Due to this mismatch, the actual HARD_CAP
is set to 110,000,000 * 10^18
tokens, instead of the likely intended 110,000,000 * 10^9
tokens.
This results in a HARD_CAP
that is 1,000,000,000 (one billion)
times larger than intended.
Correct the HARD_CAP definition to align with the 9 decimal places of the DNX token:
uint256 public constant HARD_CAP = 110_000_000 * 10**9;
SOLVED: The suggested mitigation was implemented by the Dynex team.
// Informational
The BridgeEVM contract contains a critical vulnerability in its implementation of the minSend
check. The check is performed on the amountWithFee
instead of the actual amount being sent, allowing for potential bypass of the minimum send limit.
The _sendTokens
function checks the minSend
requirement against amountWithFee
:
if (minSend != 0 && amountWithFee < minSend) revert NotReachedMinLimit();
The actual amount sent is calculated as:
amount = amountWithFee - fee;
This implementation allows for a scenario where:
amountWithFee
equals fee
fee
is greater than or equal to minSend
The resulting amount
sent is zero
Modify the _sendTokens
function to check the minSend
requirement against the actual amount being sent, not amountWithFee
:
uint256 actualAmount = amountWithFee - fee;
if (minSend != 0 && actualAmount < minSend) revert NotReachedMinLimit();
SOLVED: A new check was added which ensures that minSend
is always greater than fee:
if (_minSend <= _fee) revert IncorrectMinSend();
// Informational
The BridgeEVM::sendTokensWithPermit
function processes a permit, suggesting support for third-party transfers.
However, in the _sendTokens
function (called by sendTokensWithPermit
), tokens are always transferred from and burned from the msg.sender
:
if (fee != 0) dnx.safeTransferFrom(msg.sender, feeWallet, fee);
dnx.burnFrom(msg.sender, amount);
This implementation requires the function caller to be the token owner, negating the primary benefit of using a permit.
To address this vulnerability, properly implement the permit functionality.
ACKNOWLEDGED: The Dynex team acknowledged the finding with the following comment: "The idea of using permit in our case was to allow the user to send tokens by calling 1 transaction (send Tokens With Permit) instead of 2 (approve + sendTokens) for better UX."
// Informational
The receiveTokensBatch
function in the BridgeEVM contract lacks a check to ensure that the lengths of the transactions
and signatures
arrays match. This oversight could lead to unexpected behavior and potential security risks.
function receiveTokensBatch(Transaction[] calldata transactions, bytes[] memory signatures) external {
uint256 length = signatures.length;
for (uint256 i = 0; i < length; ++i) {
receiveTokens(transactions[i], signatures[i]);
}
}
Add a check at the beginning of the function to ensure array lengths match:
function receiveTokensBatch(Transaction[] calldata transactions, bytes[] memory signatures) external {
require(transactions.length == signatures.length, "Array length mismatch");
uint256 length = signatures.length;
for (uint256 i = 0; i < length; ++i) {
receiveTokens(transactions[i], signatures[i]);
}
}
SOLVED: The suggested mitigation was implemented by the Dynex team.
uint256 length = signatures.length;
if(length != transactions.length) revert IncorrectParameterLength();
Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contracts in scope. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified the smart contracts in the repository and was able to compile them correctly into their ABIs and binary format, Slither was run against the 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.
The security team conducted a comprehensive review of all findings generated by the Slither static analysis tool.
After careful examination and consideration of the flagged issues, it was determined that within the project's specific context and scope, all were false positives. Including the reentrancy, as the call is being made to DNX contract (trusted)
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