As of 3 Nov 2026, Nemeos has 1 audit report on Trustblock, by Quantstamp, dated 15 Mar 2024. That report lists no open critical findings.
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 |
|---|---|---|---|---|---|
| Nemeos | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 6 |
| Medium | 0 | 2 | 3 |
| Low | 2 | 7 | 4 |
*Note: This issue was disclosed by the Nemeos team before the start of the security review.* Each deployed pool is associated with a fixed NFT collection (external address) and a minimal deposit percentage. However, `Pool.buyNFT()` accepts `collectionAddress_` as a parameter, breaking that association. This could allow borrowers to buy NFTs from other arbitrary collections using the pool funds allocated for another collection.
Open the reportThe `refundLoan()` function currently updates the `loan.nextPaymentTime` by directly adding the `MAX_LOAN_REFUND_INTERVAL`, which introduces a discrepancy in loan terms. Specifically, taking the case of a loan requested for 45 days, this calculation method allows the borrower to accrue interest for only the initial 45 days but grants an extension to repay the principal amount up to 60 days. This discrepancy arises because the `MAX_LOAN_REFUND_INTERVAL` is statically added to the `nextPaymentTime` without considering the original remaining loan duration which should be 15 days.
Open the reportWhen a borrower repays the full loan amount, the NFTWrapper token associated with that loan is burned, and the underlying NFT is sent to the original borrower that created the loan. However, this behavior can be unexpected, as the NFTWrapper mints ERC-721 tokens that can be transferred to other addresses. Other users can exchange tokens by NFTWrapper tokens, but in the end, they will not get the underlying NFT.
Open the reportThe `Pool.redeem()` and `Pool.withdraw()` functions in the contract allow for withdrawals on behalf of another user, creating a potential security risk under certain conditions: - The affected user is a contract unable to manage withdrawn funds effectively. - This flaw could let attackers withdraw assets from other users, enhancing their own ROI returns. - It enables manipulation of the `Pool`'s daily interest rate, as withdrawals prompt an update to this rate. The absence of the `_spendAllowance()` logic in `Pool.withdraw()`, a feature originally included in the `ERC4626Upgradeable` contract, is the fundamental cause of this vulnerability. This mechanism is essential for verifying and upholding the spending limits designated by users, thus blocking unauthorized withdrawals.
Open the report`Pool` implements a vesting system that assigns a time period to each depositor based on the interest rate voted at deposit time. This is checked at `_withdraw()`, a function overridden from the `ERC4626` implementation of OpenZeppelin. This is easily bypassed by transferring the ERC-20 shares of the vault to another address, as `_withdraw()` checks the vesting time of `owner`, an arbitrary address passed by the caller.
Open the reportThe `_createLoan()` function in the contract calculates loan interest based on the interest rate at the time of the NFT purchase, not affecting loans set prior to any rate changes. This design permits a potential exploit where users can manipulate the interest rate by depositing assets to vote for a lower rate, buying the NFT at this reduced rate, and then withdrawing their assets. This sequence can be executed in a single transaction by executing the following transaction in order: 1. `depositAndVote()` 2. `buyNFT()` 3. `withdraw()` or `redeem()` This is possible since the `depositAndVote()` function, allows setting the `dailyInterestRate_` to zero. Consequently, users can withdraw their deposited assets anytime, as `_updateVestingTime()` calculates the locking period by multiplying `dailyInterestRate_` and `vestingTimePerBasisPoint` which will be equal to zero. An interest rate of zero effectively nullifies the locking period, enabling the withdrawal of assets and potentially allowing an attacker, especially through flash loan attacks, to lower the pool's daily interest rate significantly.
Open the reportThe functions `Pool.redeem()`, `Pool.depositAndVote()`, and `Pool.withdraw()` lack re-entrancy protection. Re-entrancy, a prevalent vulnerability in smart contracts, enables attackers to re-invoke a contract's functions before it finalizes its state, potentially leading to unintended behaviors like fund drainage, state manipulation, or logical execution errors.
Open the report`NFTFilter.verifyLoanValidity()` verifies the signature passed by the borrower and returns false if it was not signed by the Nemeos off-chain oracle. While verifying the signature, it increases an internal mapping that keeps track of all users' nonces. This is an external function without any access control. Although a valid signature is needed, it does not revert if the signer is not the oracle. Even if it reverted, an attacker could listen for `Pool.buyNFT()` transactions, get the signature from the oracle, and just call `verifyLoanValidity()`. This will increase the nonce of the user, and when the legit transaction executes it will revert.
Open the reportWhen a borrower is late in his loan payments, other users can start a liquidation process. Any address can call `DutchAuctionLiquidator.buy()` and buy the NFT at `currentPrice` (linearly decreasing from the starting loan price). `DutchAuctionLiquidator.buy()` validates that the ETH being paid is greater than this price (strict equality will not work here as the price changes with block timestamp). However, it does not refund the user buying the NFT for that difference (`msg.value - currentPrice`). This difference will be sent to the user being liquidated in the `Pool` contract (`Pool.refundFromLiquidation()`).
Open the report"Dead" code refers to code that is never executed and hence makes no impact on the final result of running a program. Dead code raises a concern, since either the code is unnecessary or the necessary code's results were ignored. Here are some snippets of dead code found in the codebase: 1. `DutchAuctionLiquidator` inherits from OpenZeppelin's `Initializable`, but it does not use the `initializer` modifier or other features of it. 1. `Pool.sol` imports the following files but may not use them: `IERC20.sol` 1. `PoolFactory.liquidator` is never used in `PoolFactory`
Open the report`NFTFilter` verifies messages signed by `oracle` (off-chain system out of scope). However, the signature does not include the verifying contract address, opening the possibility of a replay attack between contracts in the same chain.
Open the reportIn the `Pool.initialize()` function, there is an incorrect requirement statement that checks the wrong variable. The current implementation uses `minimalDepositInBPS` in the `require` statement to validate that the minimal deposit is below a predefined limit (`BASIS_POINTS`). However, this check should be applied to the parameter `minimalDepositInBPS_` passed into the `initialize` function, not the state variable `minimalDepositInBPS`.
Open the reportAccording to the project configuration files, the contracts are compiled in Solidity version `0.8.20` with an unspecified EVM version. As a result, the contract bytecode may be compiled with the default EVM version Shanghai and may include `PUSH0` opcodes, which are not supported on certain EVM chains, including BSC, [Arbitrum](https://docs.arbitrum.io/solidity-support), and [Optimism](https://community.optimism.io/docs/developers/build/differences/). The deployment or execution of the contract may fail due to the use of an invalid opcode.
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="nemeos"></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