As of 3 Nov 2026, LinoSwap has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 23 Feb 2023. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.
Newest first
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 0 | 2 | 1 |
| Medium | 0 | 0 | 1 |
| Low | 1 | 2 | 20 |
It is possible for the pool to become imbalanced so that a user cannot remove liquidity, even when they have LP tokens. This can happen when many NFT's have been swapped for in the pool, so that no matter what NFT a user chooses from those remaining in the pool, its percentage of the pool will be higher than the percentage of LP tokens a user holds. A possible scenario is as follows: - Assume a user has a high valued rare NFT. - There is a pool from the same project with only low value common NFT's such that the rare NFT is worth as much as all of the NFT's in the pool currently. - The user adds liquidity with high high valued NFT. - The user then removes all their liquidity, but removes all the low value NFT's instead of their high valued NFT. - In this case there would only be one NFT left in the pool and non of the original LP providers would be able to remove liquidity.
Open the reportThe signature for a `liquidityNFT` allows the same signature to be submitted multiple times for a replay attack. Additional NFT's may be minted in a collection changing the `rarityRankPercentage` for each NFT, in this case an old signature can be used to calculate the value based on the old `rarityRankPercentage`.
Open the reportThe function `refreshBenchmarkNFT()` changes the `_reservedBenchmarkNFT` variable and does not affect the `_reservedETH` variable. Thus, this function will alter the constant product curve by either increasing or decreasing the value of `k`. While the value of NFT's may change due to their rarity changing, this function can create an imbalance in the pool.
Open the reportWhen the `feeShare` and `royaltyShare` are set, it is only checked that they are individually less than or equal to `LinoswapV1Const.TOTAL_SHARE`. However when the fees for swaps are calculated, the `feeTotalShare` may be the sum of these values which could exceed the `LinoswapV1Const.TOTAL_SHARE`.
Open the reportThe function `validate()` in the contract `XykCurve` is never called internally and can be declared `external` as opposed to `public`.
Open the reportIn `LinoswapV1Royalty`, the public variable `royaltyShare` can be made a `uint128` to avoid needing to be cast as a `uint128` in `LinoswapV1Pool`. In addition the intermediate variable used in the functions can also be made `uint128`
Open the reportThe functions `swapForNFT()` and `swapForNFTPartly()` differ in how the NFT ids that are to be swapped out are input. `swapForNFT()` has a LiquidityNFT struct as input, which includes a signature that must be verified. While `swapForNFTPartly()` simply has an array `uint256[]` as input. As `swapForNFT()` has a signature it only allows users to swap for NFT's that a signer has signed for. While `swapForNFTPartly()` allows a user to swap for any NFT held by the pool, without needing a signature. In previous iterations, the signature was needed as the rarityRankPercentage of each NFT had to be provided. However, now the contract stores the value of each NFT via the `benchMarkNFTArray[]`, which can be updated via the `refreshBenchmarkNFT()` function if rarity rank percentages change. Considering this change, a signature is no longer necessary as any NFT held in the pool should be eligible for a swap.
Open the reportIn the contract `LinoswapV1Royalty`, there is commented out code that seems to be outdated and unused.
Open the reportIn the contract `ParetoDistribution`, the functions `getRarityValue()` and `getRarityValyeArray()` use the `memory` data location for the input `rarityRankPercentage`. However, these inputs are never modified and their 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.
Open the reportThe library `TransferHelper` is no longer used and seems to have been replaced with `SafeTransferLib`.
Open the reportThe contract `LinoswapV1Factory` has a `receive()` function with a comment stating it is to receive protocol fees. However, protocol fees are now collected via the pools themselves, so this contract no longer requires a `receive()` function. In addition, there is no withdrawal function for this contract, so any native tokens that are sent to the contract will be locked.
Open the reportIn the contract `LinoswapV1Router`: - The following functions do not check that the input `pool` is a registered pool of the factory: - `addLiquidity()`; - `removeLiquidity()`; - `removeLiquidityForETH()`; - `removeAllLiquidity()`; - `swapForNFT()`; - `swapForNFTPartly()`; - `swapForETH()`. --- In the contract `LinoswapV1Factory: - The function `addPools()` adds an address of a pool to an unused `poolID`. When pools are created, their `poolID`'s are generated as the hash of the pools `nft`, `basicCurve`, `rarityFunction`, `rarityValueCurve`, `vMax`, and `feeShare`. It is not checked that the the hash of these values from the input `_poolAddrs`, matches the input `_poolIDs`.
Open the reportAddresses should be checked before assignment or external call to make sure they are not zero addresses. In the contract `LinoswapV1Factory`: - In the function `setPoolTemplate()`, the input `_poolTemplate` should be checked it is not the zero address. --- In the contract `LinoswapV1Router`: - In the function `batchTransfer()`, the input `recipient` should be checked it is not the zero address.
Open the reportThe low-level `call` function returns the status of the call as first variable in the returned tuple. The status of the `call` is not asserted to be `true`, which would treat the low-level call as a success even when it reverted. ```solidity=271 address(tokenContract).call( abi.encodeWithSignature("transferFrom(address,address,uint256)", msg.sender, recipient, tokenIds[index]) ); ```
Open the reportThe contract `LinoswapV1Pool` does not protect its initializer. An attacker can call the initializer and change the storage of the logic contract. The clones use their own storage, so there is no risk to the pools generated by the factory, but this can cause confusion if a user sets values for the logic contract and tries to claim they hold for any of the pools created by the factory.
Open the reportIn the `_updateReserved()` function both the `amountETH` and `amountBenchmarkNFT` are of type uint256 being cast to a smaller type of `uint128` which may truncate the data. In the `_refreshBenchmarkNFT()` function both the `newBenchmarkNFT` and `currentBenchmarkNFT` are of type uint256 being cast to a smaller type of `uint128` which may truncate the data. We understand that it is very unlikely that these values will every exceed the maximum uint128, but it is possible if the supply of ETH becomes larger or if the value of the NFT's is mistakingly given a large value.
Open the reportThe event `RarityRankPercentageUpdate` is declared in `ILinoswapV1Pool` but never emitted.
Open the reportThere should always be events emitted in the sensitive functions that are controlled by centralization roles.
Open the reportThe **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.
Open the reportAddresses should be checked before assignment or external call to make sure they are not zero addresses. ```solidity=34 protocolFeeRecipient = _protocolFeeRecipient; ``` - `_protocolFeeRecipient` is not zero-checked before being used. --- ```solidity=76 protocolFeeRecipient = _protocolFeeRecipient; ``` - `_protocolFeeRecipient` is not zero-checked before being used. --- ```solidity=16 factory = _factory; ``` - `_factory` is not zero-checked before being used.
Open the reportThe `_afterTokenTransfer()` only checks that `to` is not the zero address. If the amount of token transferred is `0`, then `to` will be added to the `_liquidityProviderSet` when they do not hold any LP tokens.
Open the reportBoth `createLiquidity()` from the router and `createPoolETH()` from the factory check that `getPool[poolID] == address(0)`. Since `createLiquidity()` calls `createPoolETH()` this makes this check redundant.
Open the reportThere are the following typos in the code: - In line 63 of LinoswapV1Pool.sol, there is a commented out line that should be removed. - In line 314 of LinoswapV1Pool.sol, there is a commented out line that should be removed. - In line 346 of LinoswapV1Pool.sol, there is a commented out line that should be removed. - In XykCurve.sol, the error message should say invalid instead of invalidate.
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="linoswap"></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