Incident,
Source: De.Fi incident database.
- Audits before it
- 4.
- Audits since
- None on Trustblock.
As of 3 Nov 2026, Hashflow has 4 audit reports by 3 security firms on Trustblock; the latest is by Quantstamp, dated 22 Mar 2023. Those reports list no open critical findings. It also holds 2 deployed contracts on 2 chains and 1 incident (latest 13 Jun 2023).
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Hashflow Hashverse | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| Hashflow Token | Security firm: HashEx | Date: | Scope: 2 addresses on Ethereum and BNB Chain | No open critical findings | Open · hashex.org |
| Hashflow Token | Security firm: HashEx | Date: | Scope: 1 address on Ethereum | No open critical findings | Open · IPFS |
| Hashflow-Audit3 | 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 | 0 |
| High | 0 | 1 | 1 |
| Medium | 0 | 0 | 1 |
| Low | 3 | 5 | 20 |
When a user deposits tokens into a quest, they do so using a struct, `TokenDeposit`, that includes the address of the token and the amount that will go into `safeTransferFrom()`. Moreover, the amount of tokens that a user is allowed to trade with in a quest depends on the `portfolioTokenBalances`, which is increased by the `TokenDeposit` amount when depositing tokens via `_depositTokens()`. As a result, if the `transferFrom` function does not act strictly via the input amount when changing balances (for example, in the case of deflationary tokens), then users may be able to trade with more tokens than actually reached the contract when depositing them, as other users may have deposited funds ahead of them. Consequently, the users who trade later may be unable to trade with the full amount of what they deposited. `potfolioTokenBalances` is also used when determining how much users are able to withdraw. Hence, if the amount of a token in the contract is lower than the cumulative amount recorded in `portfolioTokenBalances`, some users will be unable to withdraw in accordance with their rightful proportion of the total funds in the contract.
Open the reportUsers can bridge their items between chains by calling `wormholeBridgeOut()` in `RenovaItem` and `RenovaItemSatellite`. The user chooses the destination Wormhole chain ID with `dstWormholeChainId`. If the user passes the chain ID for a chain that does not have a `RenovaItemSatellite` contract deployed, the item is essentially lost as the item is burnt on the source chain during the bridging. There are no relevant checks to ensure that the destination chain is configured for bridging Renova items.
Open the reportWhen bridging a Renova item to another chain, the user calls `wormholeBridgeOut()` on the source chain. After the item is burnt on the source chain, the line `delete _hashverseItemIds[tokenId];` deletes the Hashverse item ID associated with the token ID. However, after deleting the Hashverse item ID from the mapping, the event `XChainBridgeOut` calls `_hashverseItemIds[tokenId]` again to get the associated Hashverse ID. Therefore, the emitted Hashverse item ID will always be `0`.
Open the reportIn `mintItem()`, minted items are tracked using the `rootId` and `tokenOwner` address. If a `rootId` happens to reward a user multiple times, every call to `mintItem()` after the first one will fail.
Open the reportWhen creating quests, the maximum number of items is assigned to `_maxItemsPerPlayer`. In `_loadItems()`, if `_maxItemsPerPlayer` is set to zero, then the maximum number of items is `type(uint256).max`. Therefore, the smallest maximum number of items a quest can have is one.
Open the reportOne attack vector that must be accounted for when implementing cross-chain functionality is that of replay attacks. It is possible for someone to design a system implementing Wormhole that allows a VAA coming from some chain to be transmitted more than once on the destination chain, as the Wormhole core layer contract does not prevent this. Therefore, it is the responsibility of the system designer to determine whether this is a desirable trait and, if not, to implement a countermeasure. In the case of Hashverse, there are two possible cross-chain messages to consider with respect to this issue. The first message is cross-chain minting of avatars. Any address is able to mint an avatar NFT; this NFT is soulbound (the transfer function is disabled) and the first minting must be on Ethereum Mainnet. It also cannot be burned. Subsequently, users can use the `RenovaAvatar.wormholeMintSend()` function to mint this avatar NFT on another chain and to the same address as it belonged to on Ethereum Mainnet. Attempting to replay the emitted VAA would accomplish nothing as the previously cross-chain minted NFT would still be at the same address, causing the attempt at replaying the VAA to revert. The second message is cross-chain transferring of items. Items are NFTs that can only be initially minted on Ethereum Mainnet. These mints are controlled -- they either require the minting user to be directly authorized by the NFT contract owner, or they must be minted through the command deck, either directly by the admin of the command deck, or a process involving Merkle trees controlled by the admin of the command deck. Once minted, an item can be sent to another chain by use of the `RenovaItem.wormholeBridgeOut()` function -- this burns the item on the source chain and emits a VAA that when received on the destination chain will mint the item with the same `tokenId`. To prevent replaying of the VAA, each VAA is sent with a nonce associated with the item and that increases every time the item is sent to another chain. Hence, attempting to replay the VAA would only result in execution being reverted. Therefore, there are overall no concerns about cross-chain replay attacks in the current project. However, the `WormholeBaseUpgradeable` contract is an abstract contract that developers may want to reuse in other projects. It is important to note that, in general, this contract does not prevent replaying VAAs in the `_wormholeReceive()` function.
Open the reportUsers can enter a quest as long as it is before the start time and the maximum number of players has not been reached. Once a user has entered a quest, `numRegisteredPlayers` is incremented. If `_maxPlayers` is small, a malicious actor could quickly register effectively inactive players until the maximum number of players is met. This would cause the malicious actor to be the only active player in the quest and deny other users the ability to play. Additionally, when entering a quest, `numRegisteredPlayersPerFaction` is incremented for the faction the user selected. If a quest is designed to have opposing factions compete against each other, a malicious actor could quickly register inactive players for the opposing faction to increase the malicious actor's chances of winning.
Open the reportA greedy contract is a contract that can receive ether which can never be redeemed. `RenovaItemBase.wormholeBridgeOut()` is a payable function which means that users can send ETH to the contract using this function; however, at no point does this contract use any ETH in its functions, nor is there any means to withdraw ETH from the contract. Notably, ETH would ordinarily be used for interacting with Wormhole via `publishMessage()`.
Open the reportThe ordering of the parameters to the event `UpdateHashflowRouter` in `updateHashflowRouter()` is incorrect, as it currently suggests that we are updating the Hashflow router from the new router to the old one, based on the documentation of the event in the interface.
Open the reportIt is important to validate inputs, even if they only come from trusted addresses, to avoid human error. Specifically, in the following functions arguments of type `address` may be initialized with value `0x0`: * `RenovaCommandDeckBase.updateRenovaItem()`: `_renovaItem` * `RenovaCommandDeckBase.updateRenovaAvatar()`: `_renovaAvatar` * `RenovaCommandDeckBase.updateHashflowRouter()`: `_hashflowRouter` * `RenovaAvatarBase.__RenovaAvatarBase_init()`: `_renovaCommandDeck`
Open the reportThe `require` statement in `RenovaQuest.trade()#L335` checks that `balanceBefore + quoteTokenAmount <= balanceAfter`. If the trade has positive slippage, say the user receives more tokens than quoted, then the extra funds will be locked in the account. In [Hashflow's documentation](https://docs.hashflow.com/hashflow/), it is claimed that the trading platform provides zero-slippage trade, so this should not happen. If this is indeed the case, we should enforce strict equality instead, namely `balanceBefore + quoteTokenAmount == balanceAfter`.
Open the reportSolidity versions `0.8.13` and `0.8.14` [contain important bugs](https://blog.soliditylang.org/2022/06/15/solidity-0.8.15-release-announcement/), that if exploited, could result in serious issues. These bugs were fixed in `0.8.15`.
Open the reportThe Hashflow team retains the ability to update `renovaItem` by calling `RenovaCommandDeckBase.updateRenovaItem()`. If the Renova item contract is updated while a user has loaded their items into a quest, the user will not be able to unload their items once the quest is complete. This is due to `RenovaQuest.unloadAllItems()` calling `RenovaCommandDeckBase.renovaItem()` to get the Renova item address, which is then used to transfer the items back to the user.
Open the reportIn order to use Wormhole, the user must first cause the emission of a Verifiable Action Approval (VAA) by callling the `publishMessage()` function of the Wormhole core layer [implementation contract](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/Implementation.sol) . In order for this call to succeed, the call's `msg.value` field must be equal to `messageFee()`, a variable of the implementation contract. In `WormholeBaseUpgradeable`, there is a call to `publishMessage()`, but it is always with `msg.value` set to zero. Currently, `messageFee()` returns zero. However, in the future, it is possible for Wormhole to increase this fee, causing all attempts to call `publishMessage()` through `WormholeBaseUpgradeable` to revert, and hence preventing any attempts at cross-chain minting of avatars and cross-chain transferring of items.
Open the reportIn `trade()`, the function makes an ERC20 approval but does not check the return value. As a result, if the `approve()` function returns false, this may go entirely unnoticed depending on the implementation behind `IHashflowRouter`.
Open the reportThe implementation contract behind a proxy can be initialized by any address. This is not a security problem in the sense that it impacts the system directly, as the attacker will not be able to cause any contract to self-destruct or modify any values in the proxy contracts. However, taking ownership of implementation contracts can open other attack vectors, like social engineering or phishing attacks.
Open the reportThere is an inconsistency between code and comment on L654. The comments for multi-hop trades is incorrectly placed in the single-hop trade code block.
Open the reportComparison `percentWithdrawLimit >= 0`, that is always true, may be unnecessary. ```solidity=119 require( percentWithdrawLimit >= 0 && percentWithdrawLimit <= 100, 'HashflowGovernance: Number should be a percentage.' ); ```
Open the reportFor upgradeable contracts, there must be a storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise, it may be very difficult to write new implementation code. Without storage gap, the variable in the child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. Refer to https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
Open the reportIn contract `HashflowRouter`, function `tradeXChain()` proceeds without using `_isPoolAuthorized()` function to confirm if the quote.pool is allowed, whereas this check is carried out in RFQm process.
Open the reportSeveral typos were found in the comments of different files.
Open the reportThe linked functions are equivalent to the compiler-generated getter functions for the respective variables.
Open the reportAttached through a shared contract address. Our incident data ends in Oct 2026.
Source: De.Fi incident database.
| Address | Chain | Source code |
|---|---|---|
| 0xb399…cadc0xb3999f658c0391d94a37f7ff328f3fec942bcadc | Chain: BNB Chain | Source code: Verified source |
| 0xb399…cadc0xb3999f658c0391d94a37f7ff328f3fec942bcadc | Chain: Ethereum | Source code: Verified source |
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="hashflow"></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