Hinkal security record

As of 3 Nov 2026, Hinkal has 2 audit reports by 2 security firms on Trustblock; the latest is by Zokyo, dated 20 Feb 2024. Those reports list no open critical findings.

Critical findings
No open critical findings
Across 2 reports.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.

Audits2

Newest first

AuditSecurity firmDateScopeCritical findingsReport
HinkalSecurity firm: ZokyoDate: Scope: Not recorded (see the report)No open critical findingsOpen · IPFS
Hinkal ProtocolSecurity firm: QuantstampDate: Scope: Files named in the reportNo open critical findingsOpen · certificate.quantstamp.com

Findings47

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

SeverityOpenAcknowledgedFixed
Critical000
High004
Medium125
Low0332
HighAll Funds Become Irredeemable when the Tree Is Halfway PopulatedHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The `insert()` and `insertMany()` functions of `Merkle` fail to update the top root in the Merkle tree if all of the leaves in the left subtree are filled, i.e., the entire tree is half-filled. For example, `insertOne()` saves gas by caching node values if they are right-side nodes and only writes values to storage for left-side nodes. The implementation can be seen below: ``` function insertOne( uint256 currentNodeIndex, uint256 twoPower, uint256 prevHash ) internal { for (uint256 i = 0; i <= twoPower; i++) { if (currentNodeIndex % 2 == 0) { tree[i] = prevHash; // Left side - value stored if (i != twoPower) prevHash = hash(prevHash, 0); } else { prevHash = hash(tree[i], prevHash); // Right side - value cached } currentNodeIndex /= 2; } } ``` Once the tree is halfway full, `twoPower` will be large enough such that `currentNodeIndex == 1` for the root. However, a `currentNodeIndex` of `1` will be cached locally to `prevHash` rather than written to storage. The commitment will be accepted from the user, but they will not be able to later prove its existence, making the funds irredeemable. In fact, funds will eventually become irredeemable for all users. The `tree[twoPower]` value will still be zero, and it will be stored as a valid root in the `root[]` mapping. Once the `root[]` mapping contains nothing but zero roots, all commitments would become un-nullifiable, resulting in all funds being stuck in the protocol.

Open the report
HighCommitments Can Be Overwritten by Overflowing the TreeHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

There are two Merkle Tree implementations: `Merkle` and `MerkleRemovable`. Both inherit from `MerkleBase` and make sure that no more leaves can be inserted than the maximum number of leaves in a tree of `LEVELS` levels. The `insert()` function performs the above check with the following guard: ``` require(newIndex != uint256(2) ** LEVELS, "Tree is full."); ``` This has two consequences: 1. The last leaf can never be added to the tree, as `newIndex == uint256(2) ** LEVELS`. 2. The guard does not revert for `newIndex > uint256(2) ** LEVELS`. Once the tree limit is exceeded, a new level of depth under the previous leaves is created to which new leaves are appended. These newly inserted leaves would overwrite their parent node (the previous leaves containing commitments) when propagating the update toward the root. For example, if the `( 2 ** LEVELS ) + 1` element were to (incorrectly) be inserted into the tree, its parent node would suddenly be the leaf of the first commitment ever inserted, as `( ( 2 ** LEVELS ) + 1 ) / 2 = MINIMUM_INDEX`. This element would get overwritten when calculating root updates, invalidating its potentially un-nullified, previously contained commitment. Therefore, subsequent insertions via `insertMany()` (and `insert()` once the tree has overflowed) would continue to overwrite leaves and invalidate their stored commitments.

Open the report
HighMissing/Limited Test SuiteHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

We were unable to run the test suite with the instructions provided. A deeper look into the test files revealed further concerns. Many of the contracts were being initialized with outdated constructors. The tests for the protocol's core functionality, the merkle tree and Hinkal transactions and swaps, were being skipped entirely.

Open the report
HighThe Protocol Can Be Drained Through Commitment DuplicationHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Commitments are intended to be nullified once spent to prevent users from reusing commitments in future transactions. However, it is possible to insert commitments without nullifying previous commitments. This is done by exploiting the construction of the input array to the Circom Verifiers. The duplicated commitments can later be withdrawn. With enough duplicated commitments, the entire protocol can be drained.

Open the report
MediumA Transaction May Not Distribute All FundsHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

After completing the transactions, `_transact()` loops through the exchanged tokens to compare the changes in the token balance seen to the changes in the token balance expected by the provided proof. It then checks to confirm that the contract has not distributed more tokens than expected: ``` require( balanceDif >= circomData.amountChanges[i], "Inbalance in token detected" ); ``` This creates an imbalance where the contract can keep more tokens than expected. Though this might be in place due to unforeseen transfer amounts from integrations with external protocols, this still allows for incomplete withdrawals.

Open the report
MediumAttackers Can Make Protocol Interactions Untraceable for UsersHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

When calling a `transact()` function on the `Hinkal` contract, a parameter `encryptedOutputs` is given and logged in the `NewCommitment()` event. According to the documentation and code comments, `encryptedOutputs` are _"Output UTXOs encrypted with encryption key of the user making call"_. Hinkal users fetch the event logs and decrypt the `encryptedOutputs` using the private key from their shielded addresses to retrieve their UTXOs. However, since no integrity check of `encryptedOutputs` exists in the Solidity contracts or Circom circuits, a malicious relayer or a front-runner can arbitrarily change its value to alter the decrypted output to some random bytes. The only way they could find the matching commitments, in this case, would be by brute-forcing the `blindings` value _for each leaf in the Merkle tree added since making the request_. This may not be possible should the tree reach a large enough size. As a result, the user will not be able to retrieve their UTXOs correctly, which may lead to loss of funds. An attacker does not gain anything from tampering `encryptedOutputs`. As relayers are whitelisted and maintained by the client on launch, the risk is further mitigated. However, the ability of a relayer to significantly sabotage the protocol is nonetheless significant, and the vulnerability still exists for front-runners.

Open the report
MediumNo On-Chain Enforcement of Most FeesHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Relayer fees are only enforced on-chain for token withdrawals. For external protocol interactions and internal transfers, the relayer earns fees based on the difference between unlocked value from nullified commitments and locked value from newly created commitments. The fee is therefore implicit and especially for external protocol interactions not trivial to calculate. The client clarified that off-chain price oracles are used to calculate the fees and the appropriate `amountChanged` values for the tokens involved in the trades. However, there is little on-chain protection for end users in these settings. If the price source returns incorrect values or the front-end gets compromised, relayers could profit greatly through favorable `amountChanged` values.

Open the report
MediumRelayers Do Not Receive Their FeesHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

When a token withdrawal is submitted to Hinkal through a relayer, relayers can receive a fee depending on the token transferred and the amount transferred. However, the formula to calculate these fees in `_internalTransact()` is incorrect due to the usage of parenthesis: ``` relayFee = uint256(-circomData.amountChanges[i]) * (relayPercentage / 10000); ``` Since the allowed range of values for the variable `relayPercentage` is `[0:500]`, the amount will always be multiplied by `0`, resulting in the `relayFee` taking the value `0`.

Open the report
MediumSignature Maleability and False Access Token Approval for Ownerless ContractsHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The function `recoverSigner()` uses the precompiled `ecrecover()` without adding checks to prevent its underlying risks. It makes it vulnerable to two common issues: 1. The `address(0)` is returned as the default error value. 2. Signature malleability. If the owner of the contract is renounced for any reason, then all calls to `addToken()` will pass `require(signer == owner())`, thereby allowing anyone to acquire an access token. This is because the `recoverSigner()` function will return `0` if the signer address cannot be recovered. Regarding signature malleability, it is a characteristic of the ECDSA (Elliptic Curve Digital Signature Algorithm) cryptographic algorithm used in Ethereum and Solidity. It refers to the ability to produce different valid signatures for the same message by modifying the signature parameters. This can occur due to certain mathematical properties of the ECDSA algorithm. In the context of Solidity, signature malleability can have security implications, particularly when signatures are used for authentication, verification, or as proof of authorization. Exploiting signature malleability can potentially lead to unintended behaviors or vulnerabilities in smart contracts. One common form of signature malleability in Solidity's ECDSA is the "s-value malleability". In an ECDSA signature, there are three components: the "r", "s" and the "v". The "s" value is the one susceptible to malleability. Any modification to the "s" value while keeping the signature valid can result in a different but still valid signature.

Open the report
LowComponents With Known VulnerabilityHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
Show 10 more
LowInconsistency in SSL/TLS Configuration Between Domain and ELB Endpoint(All Servers and Relayers)Hinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowInsufficient Signature VerificationHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowInsufficient Validation Checks in react/src/hooks/useSwapHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowLack of Input Validation in react/src/hooks/useDepositHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowPath Disclosure in Error Response(All Relayers/Servers URLs)Hinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowPotential Exposure to Enumeration AttacksHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowRemove/Resolve TODO commentsHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
LowServer Version DisclosureHinkal · Zokyo · 20 Feb 2024Fixed

The report gives no description here.

Open the report
Low`multiTransferFrom()` Could Lead to Draining All Native TokensHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The `Transferer.multiTransferFrom()` function calls `transferERC20TokenFromOrCheckETH()` in a loop to transfer ERC-20 tokens from the caller or check an exact value of ETH is provided in the function call by `msg.value`. However, since `msg.value` is preserved in the entire function call, an attacker can provide multiple `address(0)` (represented as ETH) in the `erc20TokenAddresses` array and pass the checks but provide less `msg.value` than needed. Contracts using the `transferERC20TokenFromOrCheckETH()` may result in incorrect internal accounting or a shortage of funds. Though the function is not used in the current system, if it is improperly used in a future version of the system, it could lead to the contract being drained from its native tokens.

Open the report
LowApplication Monitoring Can Be Improved by Emitting More EventsHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

In order to validate the proper deployment and initialization of the contracts, it is a good practice to emit events. Also, any important state transitions can be logged, which is beneficial for monitoring the contract, and also tracking eventual bugs or hacks. Below we present a non-exhaustive list of events that could be emitted to improve application management: * `AccessToken.withdraw()` * `ERC20TokenRegistry.changeState()` * `ERC20TokenRegistry.addERCToken()` * `ERC20TokenRegistry.removeToken()` * `ERC20TokenRegistry.setTokenLimit()` * `Merkle.insert()` * `Merkle.insertMany()` * `MerkleRemovable.findAndRemove()` * `MerkleRemovable.insert()` * `MerkleRemovable.cleanRoots()`

Open the report
Show 10 more
LowArithmetic Overflow in Circom Could Be Exploited to Drain FundsHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

According to [Circom's documentation](https://docs.circom.io/circom-language/basic-operators/#arithmetic-operators), the arithmetic operations in the circuits are calculated modulo `CIRCOM_P`. The swapper circuits (`SwapperM`, `SwapperMO`, and `SwapperToken`) calculate the input amount and output amount sums for each token and check whether the difference is the expected amount change. Because the addition operations work modulo `CIRCOM_P`, it is possible for the result to overflow when adding up the sum of input or output amounts. As a result, an attacker could forge some output commitments with a large token amount but whose sum results in a small number. They can then drain the funds in the protocol with the forged commitments. This attack does not exist for the currently implemented Swapper circuits, but it may be introduced with new circuits if they use vulnerable constructor arguments.

Open the report
LowCritical Role Transfer Not Following Two-Step PatternHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Contracts inherit the contract `Ownable`, where the owner role can be transferred to another address simply by calling the function `transferOwnership()`. This function immediately transfers a high-level privilege to new addresses in a single transaction, which can be risky from a security perspective, as providing a faulty address may lock out that role from future calls. A more secure pattern for such privilege transfers is to require the new pending addresses to issue an `acceptAdmin()` function call before finalizing the transfer. Note that this pattern is common even with the use of timelocks, such as the Compound Timelock contract.

Open the report
LowDeletion of Access Token Will Cause All Pending Hinkal Transactions to RevertHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The deletion of an access token via the `AccessToken.blacklistAccessKey()` function removes a leaf node from the `MerkleRemovable` tree, requiring to update of the values for all parent nodes, including the root. As a necessary measure, all the cached roots need to get removed to assure that the blacklisted access key can no longer use the protocol. However, this will cause all pending Hinkal transactions by legitimate users to fail, as the `HinkalHelper.performHinkalChecks()` will revert, as the `rootHashAccessToken` those transactions used to construct the proofs are no longer valid.

Open the report
LowFunds Can Be Locked if Sent via Direct TransfersHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The purpose of the contract `Hinkal` is to receive deposits from external actors. Tokens should be deposited into the contract by whitelisted actors via a specific flow and the execution of the function `Hinkal._transact()`. However, it is also possible to send these tokens via a direct token transfer. If the logic of the contract is not clear for an external user of the system, they may deposit tokens via a direct transfer. In that case, the amount would not be accounted for by the contract and remain stuck in the contract.

Open the report
LowImpact of Using `tx.origin` to Check the RelayersHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

When a transaction is submitted to Hinkal, the function `HinkalHelper.relayerIsValid()` requires that the relayer is whitelisted. However, the function checks the value against `tx.origin`. This has two consequences: 1. A relayer cannot be a smart contract. 2. If the transactions initiated by a given relayer are not restricted to relaying Hinkal transactions, the relayer can initiate a transaction that contains the execution of a malicious contract. In this case, the malicious contract can trigger an external call to `Hinkal.transact()` with the address of the relayer in `circomData`. The impact is limited here because the rest of the data in `circomData` should be accepted by the system and may likely result in a reverted transaction.

Open the report
LowIncomplete Support for NFT Collections Where `tokenID` Can Be `0`Hinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The system supports transfers of `ERC20` and `ERC721` tokens. Several contracts distinguish if the expected token to be transferred is an `ERC20` or `ERC721` based on the parameter `tokenId`: * `== 0` will be considered as an `ERC20` transfer. * `> 0` will be considered as an `ERC721` transfer of that specific `tokenId`. However, as mentioned in the `ERC721` [standard](https://eips.ethereum.org/EIPS/eip-721), a `tokenId` of value `0` would be valid: > Every NFT is identified by a unique uint256 ID inside the ERC721 smart contract. This identifying number SHALL NOT change for the life of the contract. The pair (contract address, uint256 tokenId) will then be a globally unique and fully qualified identifier for a specific asset on an Ethereum chain. While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a “black box”. As a result, any attempt to operate with an `ERC721` token having the id `0` may revert or result in an unexpected output since the token will be incorrectly considered as an `ERC20` instead of an `ERC721`. Impacted functions include: * `CircomDataBuilder.formInputForCircom()` where the function `formInputNormal()` will be called instead of the function `formInputNft()`. * `Hinkal._internalTransact()` where the function `transferERC20TokenOrETH()` will be called to pay a relayer fee and will most likely revert. * `Transferer.getBalancesForArray()` and `Transferer.getBalancesForArrayMemory()`, where the function `getERC20OrETHBalance()` will be called instead of `getNftBalance()`. * `Transferer.transferTokenFrom()`, where the function `transferERC20TokenFromOrCheckETH()` will be called instead of `transferNftFrom()`. * `Transferer.transferToken()`, where the function `transferERC20TokenOrETH()` will be called instead of `transferNftFrom()`. * `Transferer.approveToken()`, where the function `unsafeApproveERC20Token()` will be called instead of `approveERC721Token()`.

Open the report
LowInsufficient Validation of Access Token ExistenceHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The Hinkal protocol implements a whitelist mechanism for users, where users have to obtain an access token via the KYC process and add the token to the registry before using the protocol. Access keys are calculated by `hash(shieldedPrivateKey, publicKey)` and are stored as the leaves of the `AccessToken` Merkle tree. The `AccessTokenChecker` circuit is to ensure that (1) a user's access key is in the Merkle tree and (2) they know some `X` and `Y` such that `hash(X, Y)` equals to their access key. Since the entire `AccessToken` Merkle tree is public on-chain, an attacker can generate a ZK proof to prove an internal node of the Merkle tree is in the tree by setting `X` and `Y` to its left and right child. The condition of `hash(X, Y)` being the internal node holds naturally. The attacker does not have to obtain an access token in the first place to generate a valid proof. Notice that this issue may not currently impact the protocol given that the swapper circuits calculate a user's `publicKey` as the hash of their `shieldedPrivateKey`. However, the design of the `AccessTokenChecker` circuit poses a security risk and could be vulnerable if used separately.

Open the report
LowMalicious ERC-20 Tokens Can Create Unredeemable CommitmentsHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

In addition to native tokens, the protocol also enables arbitrary ERC-20 tokens to be interacted with in the protocol. The protocol includes an `ERC20TokenRegistry` contract that can theoretically maintain a list of whitelisted ERC-20 tokens, but the auditors were informed that this contract will be disabled and not used. Enabling the support for arbitrary ERC-20 tokens is always a challenge. Malicious tokens can break assumptions about standard functions such as `balanceOf()` and others. It could grant certain addresses infinite approval to transfer tokens back from Hinkal, make them non-withdrawable from the protocol, and many other things. For such malicious token implementations, the UTXOs within Hinkal, or in other words the commitments in the Merkle tree, would not necessarily match the balance of Hinkal. While these tokens most likely would not be of any value, we still wanted to raise this as an informational issue.

Open the report
LowMissing Input ValidationHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

It is important to validate inputs, even if they only come from trusted addresses, to avoid human error: * `AccessToken.constructor()`: Set a maximum limit for the value of `_mintingFee`. * `AccessToken.setMintingFee()`: Set a maximum limit for the value of `_mintingFee`. * `ERC20TokenRegistry.contructor()`: Validate that `intialTokenSet[]` doesn't contain the zero address. * `ERC20TokenRegistry.addERCToken()`: Validate that `erc20Token` is not the zero address. * `Hinkal.registerExternalAction()`: Validate that `externalActionId` and `externalActionAddress` are both non-zero. * `Hinkal.transactWithExternalActionAndHook()`: Validate that `circomData.externalAddress` is not the zero address. * `HinkalHelper.checkTokenRegistry()`: Validate that the input arrays are the same length. * `HinkalHelper.performHinkalChecks()`: * Validate that `circomData.erc20TokenAddresses.length == circomData.amountChanges.length == circomData.tokenIds.length`. * Validate that for all `i` with `circomData.tokenIds[i] != 0`that `amountChanged[i]` is equal to one or minus one. * `RelayStore.addOrSetRelay()`: * Validate that the `_relayAddress` is not the zero address. * Validate that the `_url` is non-empty. * `Transferer.multiTransfer()`: Validate that the input arrays are the same length. * `Transferer.multiTransferFrom()`: Validate that the input arrays are the same length. * `Transferer.getBalancesForArrayMemory()`: Validate that the input arrays are the same length. * `Transferer.getBalancesForArray()`: Validate that the input arrays are the same length. * `Transferer`: All transfer functions should require transferring a non-zero amount. * `Merkle.insertMany()`: Validate that the input array `leaves` is non-empty. Otherwise, it could result in a copy of the value `roots[rootIndex]` in `roots[(rootIndex+1) % MAX_ROOT_NUMBER ]`. This could ultimately become a way to limit partially DoS the system by making sure that the history of the last `MAX_ROOT_NUMBER` valid roots contains the same value. * `MerkleBase.constructor()`: * Validate that `poseidon` is a non-zero address. * Valdiate that `LEVELS` is initialized to be less than 128. * `MerkleBase.rootHashExists()`: Validate that `_root` is non-zero. * `VerifierFacade.registerVerifier()`: * Validate that `verifierMap[verifierId] == 0` before the update. * Validate that `verifierAddress` and `verifierId` are both non-zero. <br> **Circom Circuits:** There are no guarantees that the input values to parameters like addresses are valid. For example, if the `SwapperToken` circuit is used and the wrong `nftPosition` is specified, some NFTs may be treated as fungible tokens. Update `P` and check that variables for all fields have valid integer interpretations.

Open the report
LowNo Distinction Between Metadata for Pre- and Post-Transaction HooksHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Hinkal transactions can optionally be executed with: 1. A pre-transaction hook which calls a contract at the address `circomData.preHookContract` with the calldata `circomData.hookMetadata`. 2. A post-transaction hook which calls a contract at the address `circomData.hookContract` with the calldata `circomData.hookMetadata`. If a user wants to execute both a pre-transaction hook and a post-transaction hook in the same transaction, the hooks will have to use the same `circomData.hookMetadata`. As a result, specific customization will be required on each of these contracts to parse the received calldata and cover both cases: * The contract is called without the other hook. * The contract is called in addition to the other hook.

Open the report
Show 10 more
LowOwnership Can Be RenouncedHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

If the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed. This is especially important to note for the `AccessToken` contract, as an `owner` of `address(0)` would allow anyone to add an access token.

Open the report
LowRelayer Addresses May Be Removed UnexpectedlyHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The `RelayStore.removeRelay()` function iterates the `relayStore` array to find a specified relayer address and removes it from the array. However, due to a coding error, the functions may pop excessive elements from the array, therefore removing other relayer addresses. Additionally, the current design faces gas concerns, as updates and removals require the traversal of potentially the entire array. With a sufficiently high number of relayers, block gas limit could be reached, resulting in relayers added very late to be unremovable or updateable.

Open the report
LowThe Merkle Tree Reports Having an Incorrect Root HashHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The `Merkle` contract inherits from the `MerkleBase` and enables immutably inserting commitments very similarly to Tornado Cash. However, the `getRootHash()` function inherited from `MerkleBase` seems to have been designed only with the `MerkleRemovable` contract in mind, which stores all the elements of the tree on-chain in the `tree` mapping, compared to `MerkleBase`, which only uses the `tree` mapping as a caching mechanism to keep track of the right-most, non-zero, left element of each level. While `tree[1]` is intended to be the root element in `MerkleBase` and `MerkleRemovable`, it is the right-most, non-zero, left element of the first level, i.e. the level right above the leaves, in the `Merkle` contract. This index stores the hash of the two most recently added leaves to the tree. An attacker can make the tree seem to observers to have a particular root hash by adding two commitments in a transaction. Ultimately, `Merkle.getRootHash()` will not return the root hash. This breaks any current or future integrations that might use the function.

Open the report
LowThe Protocol Can Have No RelayersHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The system can remain without any relayer if the last relayer is removed via the function `removeRelay()`. Transactions would temporarily remain unrelayable until a new valid relayer is added via `addOrSetRelay()`.

Open the report
LowThe Use of `SafeERC20.safeApprove()`Hinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The code relies on function `SafeERC20.safeApprove()`. The function shall not be used instead of `approve()` since: 1. It can still be front-run. 2. It uses additional gas. 3. It does not work with tokens that are not ERC-20 compliant. For more information see [this issue](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2219).

Open the report
LowUnclear Registering and Blacklisting FunctionalityHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The `AccessToken` contract contains a registering and blacklisting functionality that remains unclear. According to a message from the client, the `register()` function seems to have been used at one point in time to create a link public between an EOA and a hashed address. This link is however only emitted via events and can technically be called from any address by other users. A call to the `register()` function has no on-chain consequences. We did not receive clarification on the purpose of the blacklist, as it seems to have no on-chain consequences other than preventing access token signature submissions by a particular address, but the same signature can be submitted from another address without any issues.

Open the report
LowUnlicensed Contract FilesHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Solidity contract files can be explicitly licensed using the `SPDX-License-Identifier` tag at the beginning of the contract. While contracts can be licensed with a wide variety of license types and constraints, the contracts in the scope of this audit are tagged as `UNLICENSED`, allowing unconstrained reproduction, modification, and use of these contracts.

Open the report
LowUnlocked PragmaHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Every Solidity file specifies in the header a version number of the format `pragma solidity (^)0.8.*`. The caret (`^`) before the version number implies an unlocked pragma, meaning that the compiler will use the specified version _and above_, hence the term "unlocked".

Open the report
LowUse Battle-Tested `Logarithm2()` LibraryHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

The `MerkleBase.logarithm2()` function is not a standard implementation of the underlying math. More battle-tested implementations should be used instead.

Open the report
LowUse of Rebasing Tokens is ProblematicHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Rebasing tokens could be problematic for the protocol, as users might not be able to consume the entire value a commitment should technically hold, leading to strange errors. If the balance of a token inside Hinkal drops without a Hinkal transaction (e.g. due to a rebase or a confiscation burn), the users will be able to withdraw their balances on a first-come first-served basis, as long as the protocol has some balance left. When the tokens run out, the users will not be able to withdraw. Users should be discouraged from using rebasing tokens in the protocol.

Open the report
Show 1 more
LowUser Deposits Can Become Stuck if the Protocol Is BlacklistedHinkal Protocol · Quantstamp · 29 Sep 2023Fixed

Some tokens, such as USDC, have a blacklisting feature. If the protocol is ever blacklisted from interacting with a token, all user deposits would freeze for that token, as users would be unable to withdraw it and interact with it from external protocols.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

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.

Content

Facts

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.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="hinkal"
></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