As of 30 Oct 2026, Haven1 has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 4 Mar 2025. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Haven1 - Permission Manager | Security firm: CertiK | Date: | Scope: Files named in the report | 4 critical findings, fixed | Open · skynet.certik.com |
| Haven1 - audit hSwap | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 4 |
| High | 1 | 0 | 1 |
| Medium | 1 | 0 | 2 |
| Low | 11 | 0 | 12 |
The issue occurs when the `permissionsInterface.setPolicy("_nwAdminOrg", "NONE", "_oAdminRole")` function is called, which assigns the `"NONE"` role to the `_nwAdminRole`. This leads to unintended behavior in the system, as the role `"NONE"` is effectively treated as a valid role, bypassing proper access control mechanisms. Specifically, when calling `permissionsInterface.isNetworkAdmin(address(0xDEADBEEF))`, it incorrectly returns **true**, granting network admin privileges to any address assigned the `"NONE"` role. This allows unauthorized users to gain network admin status, undermining the security model of the application. The issue is caused by improper handling of the `"NONE"` role, which should not grant administrative privileges.
Open the reportThe issue arises because the role `"NONE"` is being assigned through the function `permissionsInterface.addNewRole("NONE","_orgId",3,true,true)`, which inadvertently bypasses the intended role-based permission checks. When the `"NONE"` role is assigned, it causes the function `permissionsInterface.isOrgAdmin(address(0xDEADBEEF),"_orgId")` to always return true for the given address (e.g., `0xDEADBEEF`). This could allow unauthorized users or addresses to gain admin privileges, effectively bypassing the access control mechanism and potentially compromising the system's security. The issue is caused by the improper handling of the `"NONE"` role, leading to unintended behavior in the role validation logic.
Open the reportThe smart contract suffers from a lack of access control, allowing any user to invoke sensitive functions such as: - `init()` - `setPolicy()` - `addAdminNode()` - `addAdminAccount()` - `updateNetworkBootStatus()` These functions are critical for managing admin nodes, accounts, and policies, and their unrestricted accessibility poses severe security risks. An attacker could exploit this flaw to gain unauthorized privileges, disrupt the system, or compromise its integrity, potentially resulting in financial losses, operational failures, and reputational damage.
Open the reportThe issue lies in using the zero address (**0x0**) in the `permissionsInterface.isOrgAdmin(address(0x0), "_orgId")` function call. The problem occurs because the function incorrectly treats the zero address as a valid organization admin. This is unintended behavior, as the zero address should never be considered a legitimate admin. The use of **0x0** in this context bypasses proper validation and can potentially grant unauthorized access, leading to security risks in the system. The function should include checks to prevent the zero address from being mistakenly recognized as an admin.
Open the reportThe issue with the `getNodeDetails()` function is that it relies on the nodeIdToIndex mapping, which is never initialized. Specifically, the check if `nodeIdToIndex[keccak256(abi.encode(_enodeId))] == 0` always returns **true** because the `nodeIdToIndex` mapping contains no entries, and thus the default value for any uninitialized key is **0**. As a result, the function fails to perform its intended logic and effectively becomes broken or useless
Open the reportThe issue arises from inconsistent handling of the active flag and the `votingStatus` mapping. ```sol=179 for (uint256 i = 0; i < orgVoterList[id].voterList.length; i++) { if (orgVoterList[id].voterList[i].active) { orgVoterList[id].votingStatus[id][ orgVoterList[id].voterList[i].vAccount ] = false; } } ``` When the voting item is initialized the `votingStatus` of each `active` voter is reset to `false`. However, when the inactive voter is activated via `addVoter()`, its `votingStatus` is not updated and can still be `true`. This leads to inconsistency.
Open the reportThe issue lies in the incorrect usage of `keccak256(abi.encode(abi.encode(_roleId)))` instead of the intended `keccak256(abi.encode(_roleId))` in the protection check. This misprint causes the condition to fail in certain scenarios, as the double `abi.encode()` results in incorrect hashing. As a result, the function fails to properly prevent the assignment of the "orgAdminRole", allowing unauthorized role assignments.
Open the reportIf an event is not indexed in a smart contract, it means that the event's parameters are not tagged with the `indexed` keyword. This has implications for how the event data can be searched and filtered when looking through blockchain logs. Without indexing, the event will still emit the data as part of the transaction log, but users won't be able to query for these events using the parameters. They'll have to retrieve the entire set of logs and manually sift through them to find events with the specific data. This can be less efficient and more time-consuming, especially on a blockchain with a high volume of transactions and events.
Open the reportOne or more parameters with `memory` data location are never modified in their functions and those functions are never called internally within the contract. Thus, their data location can be changed to `calldata` to avoid the gas consumption copying from calldata to memory. ```solidity=295 function approveNode( ``` `approveNode` has memory location parameters: `_enodeId`, `_ip`, `_orgId`. ```solidity=150 function addSubOrg( ``` `addSubOrg` has memory location parameters: `_pOrgId`, `_orgId`, `_enodeId`, `_ip`. ```solidity=674 function startBlacklistedNodeRecovery( ``` `startBlacklistedNodeRecovery` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=301 function approveOrg( ``` `approveOrg` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=599 function addNode( ``` `addNode` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=123 function approveOrg( ``` `approveOrg` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=254 function addNode( ``` `addNode` has memory location parameters: `_enodeId`, `_ip`, `_orgId`. ```solidity=70 function addRole( ``` `addRole` has memory location parameters: `_roleId`, `_orgId`. ```solidity=337 function updateNodeStatus( ``` `updateNodeStatus` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=267 function addOrg( ``` `addOrg` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=348 function checkOrgActive(string memory _orgId) public view returns (bool) { ``` `checkOrgActive` has memory location parameters: `_orgId`. ```solidity=234 function addAdminNode( ``` `addAdminNode` has memory location parameters: `_enodeId`, `_ip`, `_orgId`. ```solidity=797 function assignAccountRole( ``` `assignAccountRole` has memory location parameters: `_orgId`, `_roleId`. ```solidity=463 function connectionAllowed( ``` `connectionAllowed` has memory location parameters: `_enodeId`, `_ip`. ```solidity=306 function addNode( ``` `addNode` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=96 function addOrg( ``` `addOrg` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=363 function startBlacklistedNodeRecovery( ``` `startBlacklistedNodeRecovery` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=387 function approveBlacklistedNodeRecovery( ``` `approveBlacklistedNodeRecovery` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=274 function addOrgNode( ``` `addOrgNode` has memory location parameters: `_enodeId`, `_ip`, `_orgId`. ```solidity=630 function updateNodeStatus( ``` `updateNodeStatus` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=706 function approveBlacklistedNodeRecovery( ``` `approveBlacklistedNodeRecovery` has memory location parameters: `_orgId`, `_enodeId`, `_ip`. ```solidity=58 function addAdminNode( ``` `addAdminNode` has memory location parameters: `_enodeId`, `_ip`. ```solidity=344 function updateNodeStatus( ``` `updateNodeStatus` has memory location parameters: `_enodeId`, `_ip`, `_orgId`.
Open the reportAn SPDX (Software Package Data Exchange) license identifier is a standardized short-form identifier for software licenses. These identifiers are used to communicate the licensing components of software packages in a concise, consistent, and machine-readable manner. Placing the SPDX license identifier on the first line of a file is a best practice because it enhances visibility, ensures consistency, supports tool compatibility, and aids in standardizing license documentation across codebases. However, in the file, the SPDX license identifier is misplaced, thus the best practice is not followed. For more references: 1. [SPDX Official Website](https://spdx.org) 2. [GitHub Licensing a Repository Help Page**](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/licensing-a-repository) 3. [Open Source Initiative (OSI)](https://opensource.org/licenses/MIT) 4. [SPDX License List](https://spdx.org/licenses/)
Open the reportThe cited address input is missing a check that it is not `address(0)`.
Open the reportWhen attempting to index dynamic data types like `string`, `bytes`, `array`, or `struct` in Solidity, they don't get stored in their original form. Instead, the Ethereum log system stores the `Keccak-256` hash of these data types. While this approach ensures efficiency and cost-effectiveness, developers must be aware of it to correctly use and interpret logs, and they cannot retrieve the original string from its hash alone.
Open the reportIn the provided Solidity function, there is a redundant `boolean` check for `OrgIndex[keccak256(abi.encodePacked(_orgId))] != 0`. This condition is already evaluated earlier in the function, where it returns `false` if `OrgIndex[keccak256(abi.encodePacked(_orgId))] == 0`. Since the function would exit early in such cases, repeating this check in the return statement is unnecessary. This redundancy increases gas consumption and complicates the code without adding any functional value. Removing the repeated check can improve both efficiency and code readability.
Open the reportThe linked statements do not affect the functionality of the codebase and appear to be either leftovers from test code or older functionality.
Open the reportState variable that never change should be declared as `constant` to save gas. ```solidity=27 uint256 private fullAccess = 3; ``` - `fullAccess` should be declared `constant`.
Open the reportSome state variables are not used in the codebase. This can lead to incomplete functionality or potential vulnerabilities if these variables are expected to be utilized. - Variable `networkBoot` in `OrgManager` is never used in `OrgManager`. - Variable `adminOrgId` in `OrgManager` is never used in `OrgManager`. - Variable `permUpgradable` in `PermissionsInterface` is never used in `PermissionsInterface`.
Open the reportIn the current implementation of `UniversalRouter`, the contract charges the fee once per transaction upon encountering the first swap command (V3_SWAP_EXACT_OUT or V3_SWAP_EXACT_IN), then exits the loop with break. This means if multiple swap commands are present in a single transaction , only the first swap will trigger a fee. Subsequent swaps in the same transaction will not pay additional fees. ```solidity=68 for (uint256 commandIndex = 0; commandIndex < numCommands; ) { bytes1 commandType = commands[commandIndex]; uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK); if (command == Commands.V3_SWAP_EXACT_OUT || command == Commands.V3_SWAP_EXACT_IN) { _payH1Fee(); break; } unchecked { commandIndex++; } } ``` If the intended design is to collect only one fee per transaction, then the current logic is correct. If the contract should collect fees for each swap command, the logic should be adjusted.
Open the reportThe contract uses a custom storage slot for `H1NativeBaseStorage`. Using a custom storage slot is typically safe for non-upgradeable deployments, as it ensures the contract writes to a unique location unlikely to overlap with other storage variables. However, in an upgradeable proxy setup, future contract upgrades or modules may introduce new variables that use or overlap with this same slot. ```solidity bytes32 private constant H1NATIVE_STORAGE = 0x8e7ec97a86b55b46cf58cbcd08faba09d3e8d3aec4d6bf8802477f1aa7a4c700; ... function _getH1NativeStorage() private pure returns (H1NativeBaseStorage storage $) { assembly { $.slot := H1NATIVE_STORAGE } } ``` If the contract (or a child contract) is later upgraded and new variables or inherited contracts introduce different storage layouts, it could overwrite or corrupt the existing state stored in `H1NATIVE_STORAGE`. Such collisions may cause unpredictable behavior or loss of state.
Open the reportThe current contract code includes references to the `console` contract from Foundry. It is important to note that the `console` contract is exclusively intended for development and testing purposes, providing developers with debugging capabilities throughout the testing phase. ```solidity=61 function createPool( address tokenA, address tokenB, uint24 fee ) external override noDelegateCall returns (address pool) { console.log('createPool - 1'); require(tokenA != tokenB); console.log('createPool - 2'); (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); console.log('createPool - 3'); require(token0 != address(0)); console.log('createPool - 4'); int24 tickSpacing = feeAmountTickSpacing[fee]; console.log('createPool - 5'); require(tickSpacing != 0); console.log('createPool - 6'); require(getPool[token0][token1][fee] == address(0)); console.log('createPool - 7'); pool = deploy(address(this), token0, token1, fee, tickSpacing); console.log('createPool - 8'); getPool[token0][token1][fee] = pool; console.log('createPool - 9'); // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses getPool[token1][token0][fee] = pool; console.log('createPool - 10 pool: ', pool); emit PoolCreated(token0, token1, fee, tickSpacing, pool); } ``` While the `console` code may be commented out and thus inactive, its presence within the contract codebase can still negatively impact the overall tidiness and cleanliness of the contract. Comments intended for developers' understanding and debugging purposes should not be present in the final production-ready version of the smart contract.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="haven1"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us