As of 30 Oct 2026, Exactly Protocol has 8 audit reports by 3 security firms on Trustblock; the latest is by ABDK Consulting, dated 31 Oct 2025. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Exactly Protocol Update audit (October 2025) | Security firm: ABDK Consulting | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Exactly Protocol Update audit (March 2025) | Security firm: ABDK Consulting | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Exactly Protocol Update audit (April 2024) | Security firm: ABDK Consulting | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Exactly EscrowedExa Audit | Security firm: OpenZeppelin | Date: | Scope: Files named in the report | 1 critical finding, fixed | Open · blog.openzeppelin.com |
| Exactly Escrowed EXA audit | Security firm: ABDK Consulting | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Exactly Peripheral Contracts audit | Security firm: ABDK Consulting | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Exactly Protocol Phase II audit | Security firm: ABDK Consulting | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · GitHub |
| Exactly | 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 | 1 |
| High | 1 | 0 | 18 |
| Medium | 1 | 25 | 10 |
| Low | 4 | 69 | 48 |
The report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportAccording to the [Exactly project logic](https://medium.com/@exactly_finance/what-exactly-are-we-building-391d6db2692c), the protocol maintains two pools: smart pool and maturity pool. The `distributeEarningsAccordingly` function is designed to distribute a portion of fee to the smart pool. ```solidity=157 /// @notice Returns what proportional of earnings would amountFunded represent considering suppliedSP the total /// @param earnings amount to be distributed as earnings between the two participants /// @param suppliedSP current supply of the smart pool. /// @param amountFunded amount that will be checked if it came from smart pool or not. function distributeEarningsAccordingly( uint256 earnings, uint256 suppliedSP, uint256 amountFunded ) internal pure returns (uint256 earningsA, uint256 earningsB) { earningsB = earnings.fmul(amountFunded - Math.min(suppliedSP, amountFunded), amountFunded); earningsA = earnings - earningsB; } ``` According to the comment in L159, `suppliedSP` parameter is the current supply of the smart pool. Therefore, the variable `earningsA` may represent the fees that belong to the smart pool and `earningsB` represent the leftover fees. In the function `borrowMP`, the `distributeEarningsAccordingly` function will be invoked to distribute the fees. The two return values (`borrowVars.newUnassignedEarnings`, `borrowVars.earningsSP`) are respectively added to the corresponding values `pool.earningsUnassigned` and `smartPoolEarningsAccumulator`. However, the return value might have been used in the **opposite** way. That is, the **second** return value in L144 `borrowVars.earningsSP` is added to the smart pool earning records, which might conflict with the above understanding on `distributeEarningsAccordingly()` that the **first** return value should represent the fees that belong to the smart pools. ```solidity=144 (borrowVars.newUnassignedEarnings, borrowVars.earningsSP) = PoolLib.distributeEarningsAccordingly( borrowVars.fee, pool.smartPoolBorrowed(), amount ); smartPoolEarningsAccumulator += borrowVars.earningsSP; pool.earningsUnassigned += borrowVars.newUnassignedEarnings; ``` The same issue might exist in the function `withdrawMP`: ```solidity=246 (uint256 earningsUnassigned, uint256 newEarningsSP) = PoolLib.distributeEarningsAccordingly( amount - redeemAmountDiscounted, pool.smartPoolBorrowed(), redeemAmountDiscounted ); pool.earningsUnassigned += earningsUnassigned; earningsSP += newEarningsSP; ``` Therefore, it could lead to incorrect fees distributed to the smart pool.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the reportThe comment of the function `hasMaturity` states this function will clean the user's position, which does not match the function implementation. ```solidity=218 /// @dev Cleans user's position from encoded maturity pools /// @param encoded encoded maturity dates where the user borrowed /// @param maturity maturity date function hasMaturity(uint256 encoded, uint256 maturity) internal pure returns (bool) { ```
Open the report```solidity if (balance < seizeTokens) revert BalanceExceeded(); ``` Per of the context of the function `liquidate`, and the `if` condition, i.e. `balance < seizeTokens`, the error to be thrown should be `BalanceInsufficient()` instead of `BalanceExceeded()`.
Open the report```solidity receive() external payable {} ``` The `receive` function allows the contract to receive native tokens (ETH). However, if ETH is mistakenly transferred by anyone to the current contract, it could be locked.
Open the reportA reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. The `FixedLender` inherits from the `ERC4626` contract, which implementation is unknown. Furthermore, the following functions will invoke corresponding functions inherits from the `ERC4626` contract` and are possibly interact with external assets: - `withdraw()` - `redeem()` - `transfer()` - `transferFrom()` For example, if the attacker is able to successfully trigger `redeem()` invocation inside the `withdraw()` invocation, before contact states are resolved, it could lead to a potential reentrancy attacker loophole.
Open the reportIn the `repayMP` function, the following calculation may lead to a [rounding error]((https://docs.soliditylang.org/en/v0.8.1/types.html?highlight=division#division)). ```solidity=303 debtCovered = repayAmount.fmul(repayVars.position.principal + repayVars.position.fee, repayVars.amountOwed); ``` If the rounding error occurs, the `debtCovered` will be smaller than expected. Therefore, after reducing the debt, there could be some leftover in the position amd the following condition in the `if` branch will never be met: ```solidity=343 repayVars.position.reduceProportionally(debtCovered); if (repayVars.position.principal + repayVars.position.fee == 0) { delete mpUserBorrowedAmount[maturity][borrower]; userMpBorrowed[borrower] = userMpBorrowed[borrower].clearMaturity(maturity); } else { // we proportionally reduce the values mpUserBorrowedAmount[maturity][borrower] = repayVars.position; } ``` Hence, as the code in the `if` branch will not be executed due to rounding error, the position will never be fully repaid and closed.
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="exactly"></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