Hashflow Hashverse

QuantstampFiles named in the report

Quantstamp reviewed Hashflow (Hashflow Hashverse) on 22 Mar 2023: 20 findings, 0 critical; 16 marked fixed, 3 acknowledged, 1 open.

No open critical findings
Open the reportOpen Hashflow’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings20

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High001
Medium001
Low1314
HighParticipating in Quests with Tokens that Do Not Preserve Amount on Transfer May Lead to Improper AccountingFixed

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.

MediumItems Can Be Bridged to Inactive ChainFixed

Users 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.

Low`XChainBridgeOut` Event Emitted with Incorrect `hashverseItemId`Fixed

When 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`.

LowA Singular Merkle Tree Cannot Reward a User Multiple TimesFixed

In `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.

LowCannot Create Quests without ItemsFixed

When 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.

LowCode Reuse May Lead to Wormhole Replay Attack Due to VAA Hash Not Being Marked as ProcessedFixed

One 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.

LowDenial of Service for Quests when `_maxPlayers` Is SmallFixed

Users 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.

LowGreedy ContractFixed

A 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()`.

LowIncorrect Event Emission for `updateHashflowRouter()`Fixed

The 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.

LowMissing Input ValidationFixed

It 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`

Show 6 more
LowPotential Locked Funds Due to Positive SlippageFixed

The `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`.

LowSolidity Version `0.8.13` Is BuggedFixed

Solidity 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`.

LowTrapped Items when the Item Address Is Updated During Active QuestsFixed

The 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.

LowUnable to Pay Wormhole Fee During `publishMessage()`Fixed

In 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.

LowUnchecked Return Value During ERC20 ApprovalFixed

In `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`.

LowUpgradeable Contracts Should Include Constructors with `_disableInitializers()`Fixed

The 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.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (34)

  • contracts/interfaces/core/IRenovaCommandDeck.sol
  • contracts/interfaces/external/erc/IERC4906Upgradeable.sol
  • contracts/interfaces/wormhole/IWormholeBaseUpgradeable.sol
  • contracts/nft/RenovaItemSatellite.sol
  • contracts/interfaces/nft/IRenovaAvatarBase.sol
  • contracts/test/MockWormhole.sol
  • contracts/interfaces/external/IHashflowRouter.sol
  • contracts/interfaces/external/IWormholeStructs.sol
  • contracts/nft/RenovaAvatarSatellite.sol
  • contracts/core/RenovaCommandDeck.sol
  • contracts/test/HFT.sol
  • contracts/interfaces/core/IRenovaCommandDeckBase.sol
  • contracts/interfaces/staking/IStakingVault.sol
  • contracts/interfaces/nft/IRenovaAvatarSatellite.sol
  • contracts/interfaces/nft/IRenovaItemBase.sol
  • contracts/interfaces/external/IWormhole.sol
  • contracts/interfaces/nft/IRenovaItemSatellite.sol
  • contracts/interfaces/Interfaces.sol
  • contracts/test/TestERC20.sol
  • contracts/test/TestERC1271.sol
  • contracts/interfaces/nft/IRenovaAvatar.sol
  • contracts/staking/StakingVault.sol
  • contracts/nft/RenovaAvatar.sol
  • contracts/nft/RenovaItem.sol
  • contracts/test/MockHashflowRouter.sol
  • contracts/core/RenovaQuest.sol
  • contracts/wormhole/WormholeBaseUpgradeable.sol
  • contracts/interfaces/nft/IRenovaItem.sol
  • contracts/interfaces/core/IRenovaCommandDeckSatellite.sol
  • contracts/core/RenovaCommandDeckSatellite.sol
  • contracts/core/RenovaCommandDeckBase.sol
  • contracts/nft/RenovaAvatarBase.sol
  • contracts/interfaces/core/IRenovaQuest.sol
  • contracts/nft/RenovaItemBase.sol

Part of Hashflow’s security record

Hashflow
Audits4 audits by 3 firmsLast audit Mar 2023, by Quantstamp.
Critical findingsNo open critical findingsAcross 4 reports.
IncidentIncident 13 Jun 2023Source: De.Fi incident database.
Open Hashflow’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

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