Prepared by:
HALBORN
Last Updated 04/26/2024
Date of Engagement by: July 24th, 2023 - July 31st, 2023
75% of all REPORTED Findings have been addressed
All findings
8
Critical
0
High
0
Medium
0
Low
2
Informational
6
\client is a platform that enables sellers to generate invoices and buyers to settle them via their internal payment partners. The invoices generated on the platform are minted as Non-Fungible Tokens (NFTs) for increased transparency, while preserving the privacy of both buyers and sellers by keeping their information confidential.
\client engaged Halborn
to conduct a security assessment on their smart contracts beginning on 2023-07-24 and ending on 2023-07-31. The security assessment was scoped to the smart contracts provided in the truffles-nft-invoice GitHub repository. Commit hashes and further details can be found in the Scope section of this report.
The team at Halborn was provided 1.5 weeks for the engagement and assigned a full-time security engineer to verify the security of the smart contracts in scope. The security engineer is a blockchain and smart contract security expert with advanced penetration testing and smart contract hacking skills, and deep knowledge of multiple blockchain protocols.
The purpose of the assessment is to:
Identify potential security issues within the smart contracts
Verify whether the smart contracts work as expected
In summary, Halborn did not identify any significant issues; however, some recommendations were given to reduce the likelihood and impact of risks, which were successfully addressed by \client.
Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to 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 the security best practices. The following phases and associated tools were used during the assessment:
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 hot-spots or bugs. (MythX
)
Static Analysis of security for scoped contract, and imported functions. (Slither
)
Testnet deployment (Foundry
)
Code repositories:
Truffles NFT Invoice
Repository: contracts
Commit ID: 2d1a6334139ed9d6c60ff44e16c5a4198ebab737
Branch: main
Smart contracts in scope:
contracts/Truffles.sol
contracts/Authorizable.sol
contracts/Truffles.sol
contracts/Authorizable.sol
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
0
Low
2
Informational
6
Security analysis | Risk level | Remediation Date |
---|---|---|
LACK OF THE TWO STEP OWNERSHIP TRANSFER PATTERN | Low | Solved - 08/04/2023 |
MISSING ZERO ADDRESS CHECK | Low | Solved - 08/04/2023 |
INVOICE TYPE CHECK MISSING | Informational | Solved - 08/04/2023 |
LACK OF REENTRANCYGUARD | Informational | - |
LACK OF REENTRANCYGUARD | Informational | - |
REDUNDANT CHECK IN THE REMOVEAUTHORIZED FUNCTION | Informational | Solved - 08/04/2023 |
CONTRACT PAUSE FEATURE MISSING | Informational | Solved - 08/07/2023 |
FLOATING PRAGMA | Informational | Solved - 08/04/2023 |
// Low
The Authorizable
contract is inherited by the Truffles
contract and implements the Ownable
pattern. However, the assessment revealed that the solution does not support the two-step-ownership-transfer pattern. The ownership transfer might be accidentally set to an inactive EOA account. In the case of account hijacking, all functionalities get under permanent control of the attacker.
contract Authorizable is Ownable {
mapping(address => bool) private authorized;
modifier onlyAuthorized() {
require(authorized[msg.sender] || owner() == msg.sender, "Not authorized");
_;
}
contract TRUFFLES is ERC721, ERC721Enumerable, ERC721URIStorage, Authorizable {
SOLVED: The \client team solved this finding in commit fc36014: the Ownable
contract was replaced with the Ownable2Step.sol
from the OpenZeppelin library within the Authorizable
contract to establish a secure approach for conducting two-step ownership transfers.
// Low
The functions addEligibleHolder
, removeEligibleHolder
and isEligibleHolder
do not perform verification to ensure that no addresses provided as parameters are the zero addresses. Consequently, there is a risk of accidentally setting an eligible holder address to the zero address, leading to unintended behavior or potential vulnerabilities in the future.
function addEligibleHolder(address _org) public onlyAuthorized {
s_eligibleHolders[_org] = true;
}
/// @notice remove eligible holders for NFT
/// @dev remove eligible address to eligibleHolders maping : onlyAuthorized
/// @param _org address of already eligible holder
function removeEligibleHolder(address _org) public onlyAuthorized {
s_eligibleHolders[_org] = false;
}
function isEligibleHolder(address _holder)
public
view
returns (bool _isEligibleHolder)
{
return (s_eligibleHolders[_holder]);
}
SOLVED: The \client team solved this finding in commit fc36014: the function addEligibleHolder
was updated with a validation step to ensure that the provided address is not the zero address before making modifications to the headlineholders' eligibility. Similarly, the removeEligibleHolder
function now includes a validation check to confirm that the given address corresponds to an existing holder.
// Informational
The settlePrivateInvoice
function allows authorized addresses to mark the corresponding NFT of a private invoice as settled when the full amount of the invoice is paid. However, it lacks a check to verify that the provided NFT is of the private invoice type. Consequently, both private and public invoices could be settled indifferently. If the provided NFT ID corresponds to a public invoice, it is added to the s_isPrivateInvoicePaid
mapping, even though its type does not match, leading to data inconsistency.
In addition, private invoices do not store the amount for the NFT unlike public ones, so the function also does not have a proper check to verify that the full amount of the invoice is paid.
function mintPrivateInvoice(
address to,
uint256 tokenId,
string memory uri
) public onlyAuthorized {
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
s_nftType[tokenId] = NftType.PrivateInvoice;
}
function settlePrivateInvoice(uint256 _nftID) public onlyAuthorized {
require(_exists(_nftID), "NFT not minted");
s_isPrivateInvoicePaid[_nftID] = true;
}
SOLVED: The \client team solved this finding in commit fc36014: A validation step was introduced to ensure that the provided NFT ID is that of a private invoice before modifying the s_isPrivateInvoicePaid
mapping.
// Informational
// Informational
// Informational
In the Authorizable
contract, the addAuthorized
function allows the owner to add an address to the list of authorized addresses. It requires that the provided address is not the zero address, and it can only be executed by the contract owner because of the onlyOwner modifier.
The removeAuthorized
function allows the owner to remove an address from the list of authorized addresses. It requires that the provided address is not the zero address and is different from the senders, which has to be the owner.
The checks performed in the latter function are redundant, since the addAuthorized
function already prevents adding the zero address as an authorized address. Therefore, this check is unnecessary and result in extra gas overhead.
function addAuthorized(address _toAdd) onlyOwner public {
require(_toAdd != address(0));
authorized[_toAdd] = true;
}
function removeAuthorized(address _toRemove) onlyOwner public {
require(_toRemove != address(0));
require(_toRemove != msg.sender);
authorized[_toRemove] = false;
}
SOLVED: The \client team solved this finding in commit fc36014: the redundant check was removed from the removeAuthorized
function.
// Informational
It was identified that the Owner
cannot pause the Truffles
contract. In the case of a security incident, this means that the owner lacks the ability to halt the minting or settlement of Invoices, potentially leading to further complications.
SOLVED: The \client team solved this finding in commit 2e2a367: The contract now incorporates a pausing mechanism, which introduces two additional functions: pauseContract
and unpauseContract
. This enhancement is complemented by the inclusion of the whenNotPaused modifier within several functions, including mintPrivateInvoice
, settlePrivateInvoice
, mintPublicInvoice
, and _beforeTokenTransfer
.
// Informational
The Truffles
contract uses the Solidity pragma ^0.8.9. It's essential to deploy the contract with the exact compiler version and flags that have undergone thorough testing. Locking the pragma to a specific version helps to ensure that contracts are not accidentally deployed using outdated compiler versions, which might introduce bugs negatively impacting the contract system, or excessively new pragma versions that haven't undergone extensive testing.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
SOLVED: The \client team solved this finding in commit fc36014: the pragma version has been locked and upgraded to match the version used in the Authorizable
contract.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
In the manual testing phase, the following scenarios were simulated. The scenarios listed below were selected based on the severity of the vulnerabilities Halborn was testing the program for.
In the Truffles
contract, the functions mintPublicInvoice
and mintPrivateInvoice
serve the purpose of minting non-fungible tokens for public and private invoices, respectively. It is crucial to note that these functions should only be accessible to the contract owner or authorized addresses, which have been added by the owner.
To ensure the integrity of the access control mechanism and prevent unauthorized minting by malicious users or any unauthorized addresses, rigorous testing has been conducted. The tests verify that the proper access controls are in place, guaranteeing that only the designated individuals can initiate the minting process.
By carrying out these tests and enforcing the necessary access controls, the Truffles
contract maintains a secure and trusted environment for minting non-fungible tokens, safeguarding against potential security risks and unauthorized actions.
No vulnerabilities were identified.
In the Authorizable
contract, the addAuthorized
and removeAuthorized
functions enable the owner to include or exclude authorized addresses. These authorized addresses gain the privilege to call functions in the Truffles contract, enabling those addresses to extract invoices and assist in contract management.
To ensure the integrity of the system, testing was run to verify that the provided address cannot be set to zero.
No vulnerabilities were identified.
The Truffles
contract incorporates the mintPublicInvoice
and mintPrivateInvoice
functions, both dedicated to minting non-fungible tokens for public and private invoices. These functions necessitate certain values as parameters, including the tokenId.
To uphold the uniqueness and integrity of the minted invoices, comprehensive tests have been conducted to verify that no two invoices can share the same tokenId value.
No vulnerabilities were identified.
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.
Truffles
Authorizable
All the issues flagged by Slither were found to be either false positives or issues already reported, like the reentrancy issue in HAL-04 .
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 the smart contracts and sent the compiled results to the analyzers in order to locate any vulnerabilities.
The issue flagged by MythX was reported in HAL-07.
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